From 1fdec79088ed3dd7772068624a4c3e14c90a1237 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Sat, 13 Feb 2016 21:57:01 -0800 Subject: [PATCH 01/12] Start refactor for Hashicorp Heavily influenced by hraftd. rqlited builds without error. --- .gitignore | 5 + cmd/rqlited/main.go | 107 ++++++ command/execute_command.go | 90 ----- command/execute_command_test.go | 37 -- http/service.go | 121 ++++++ interfaces/interfaces.go | 8 - log/log.go | 103 ------ log/log_test.go | 131 ------- main.go | 160 -------- server/server.go | 633 -------------------------------- server/single_server_test.go | 117 ------ server/statemachine.go | 51 --- server/statemachine_test.go | 73 ---- store/store.go | 192 ++++++++++ 14 files changed, 425 insertions(+), 1403 deletions(-) create mode 100644 cmd/rqlited/main.go delete mode 100644 command/execute_command.go delete mode 100644 command/execute_command_test.go create mode 100644 http/service.go delete mode 100644 interfaces/interfaces.go delete mode 100644 log/log.go delete mode 100644 log/log_test.go delete mode 100644 main.go delete mode 100644 server/server.go delete mode 100644 server/single_server_test.go delete mode 100644 server/statemachine.go delete mode 100644 server/statemachine_test.go create mode 100644 store/store.go diff --git a/.gitignore b/.gitignore index 8e2f371b..412178bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +# Executables +rqlited +**/rqlited +!**/rqlited/ + # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a diff --git a/cmd/rqlited/main.go b/cmd/rqlited/main.go new file mode 100644 index 00000000..cfac4ac3 --- /dev/null +++ b/cmd/rqlited/main.go @@ -0,0 +1,107 @@ +/* +rqlite -- a replicated SQLite database. + +rqlite is a distributed system that provides a replicated SQLite database. +rqlite is written in Go and uses Raft to achieve consensus across all the +instances of the SQLite databases. rqlite ensures that every change made to +the database is made to a majority of underlying SQLite files, or none-at-all. +*/ + +package main + +import ( + "bytes" + "flag" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "runtime" + "runtime/pprof" + "time" + + httpd "github.com/otoolep/rqlite/http" + "github.com/otoolep/rqlite/store" +) + +var httpAddr string +var tcpAddr string +var raftAddr string +var joinAddr string +var cpuprofile string +var disableReporting bool + +func init() { + flag.StringVar(&httpAddr, "http", "localhost:4001", "HTTP query server bind address") + flag.StringVar(&tcpAddr, "tcp", "localhost:4002", "TCP query server bind address") + flag.StringVar(&raftAddr, "raft", "localhost:4003", "Raft communication bind address") + flag.StringVar(&joinAddr, "join", "", "host:port of leader to join") + flag.StringVar(&cpuprofile, "cpuprofile", "", "write CPU profile to file") + flag.BoolVar(&disableReporting, "noreport", false, "Disable anonymised launch reporting") + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage: %s [arguments] \n", os.Args[0]) + flag.PrintDefaults() + } +} + +func main() { + flag.Parse() + + // Ensure the data path was set. + if flag.NArg() == 0 { + flag.Usage() + os.Exit(1) + } + + dataPath := flag.Arg(0) + + // Set up profiling, if requested. + if cpuprofile != "" { + log.Println("profiling enabled") + f, err := os.Create(cpuprofile) + if err != nil { + log.Printf("unable to create path: %s", err.Error()) + } + defer f.Close() + + err = pprof.StartCPUProfile(f) + if err != nil { + log.Printf("unable to start CPU Profile: %s", err.Error()) + } + + defer pprof.StopCPUProfile() + } + + // Create the store. + store := store.New(dataPath, raftAddr) + + // Create the HTTP query server. + s := httpd.New(httpAddr, store) + if err := s.Start(); err != nil { + log.Printf("failed to start HTTP server: %s", err.Error()) + + } + + if !disableReporting { + reportLaunch() + } + + terminate := make(chan os.Signal, 1) + signal.Notify(terminate, os.Interrupt) + <-terminate + log.Println("rqlite server stopped") +} + +func reportLaunch() { + json := fmt.Sprintf(`{"os": "%s", "arch": "%s", "app": "rqlite"}`, runtime.GOOS, runtime.GOARCH) + data := bytes.NewBufferString(json) + client := http.Client{Timeout: time.Duration(5 * time.Second)} + go func() { + _, err := client.Post("https://logs-01.loggly.com/inputs/8a0edd84-92ba-46e4-ada8-c529d0f105af/tag/reporting/", + "application/json", data) + if err != nil { + log.Printf("Report launch failed: %s", err.Error()) + } + }() +} diff --git a/command/execute_command.go b/command/execute_command.go deleted file mode 100644 index 8fbac9c2..00000000 --- a/command/execute_command.go +++ /dev/null @@ -1,90 +0,0 @@ -package command - -import ( - "github.com/otoolep/raft" - "github.com/otoolep/rqlite/db" - - "github.com/otoolep/rqlite/log" -) - -// ExecuteCommand encapsulates a sqlite statement. -type ExecuteCommand struct { - Stmt string `json:"stmt"` -} - -// NewExecuteCommand creates a new Execute command. -func NewExecuteCommand(stmt string) *ExecuteCommand { - return &ExecuteCommand{ - Stmt: stmt, - } -} - -// CommandName of the ExecuteCommand in the log. -func (c *ExecuteCommand) CommandName() string { - return "execute" -} - -// Apply executes an sqlite statement. -func (c *ExecuteCommand) Apply(server raft.Server) (interface{}, error) { - log.Tracef("Applying ExecuteCommand: '%s'", c.Stmt) - db := server.Context().(*db.DB) - return nil, db.Execute(c.Stmt) -} - -// TransactionExecuteCommandSet encapsulates a set of sqlite statement, which are executed -// within a transaction. -type TransactionExecuteCommandSet struct { - Stmts []string `json:"stmts"` -} - -// NewTransactionExecuteCommandSet Creates a new set of sqlite commands, which -// execute within a transaction. -func NewTransactionExecuteCommandSet(stmts []string) *TransactionExecuteCommandSet { - return &TransactionExecuteCommandSet{ - Stmts: stmts, - } -} - -// CommandName of the TransactionExecute command in the log. -func (c *TransactionExecuteCommandSet) CommandName() string { - return "transaction_execute" -} - -// Apply executes a set of sqlite statements, within a transaction. All statements -// will take effect, or none. -func (c *TransactionExecuteCommandSet) Apply(server raft.Server) (interface{}, error) { - log.Tracef("Applying TransactionExecuteCommandSet of size %d", len(c.Stmts)) - - commitSuccess := false - db := server.Context().(*db.DB) - defer func() { - if !commitSuccess { - err := db.RollbackTransaction() - if err != nil { - log.Errorf("Failed to rollback transaction: %s", err.Error()) - } - } - }() - - err := db.StartTransaction() - if err != nil { - log.Errorf("Failed to start transaction: %s", err.Error()) - return nil, err - } - - for i := range c.Stmts { - err = db.Execute(c.Stmts[i]) - if err != nil { - log.Errorf("Failed to execute statement within transaction: %s", err.Error()) - return nil, err - } - } - - if err = db.CommitTransaction(); err != nil { - log.Errorf("Failed to commit transaction: %s", err.Error()) - return nil, err - } - - commitSuccess = true - return nil, nil -} diff --git a/command/execute_command_test.go b/command/execute_command_test.go deleted file mode 100644 index 2f4ff423..00000000 --- a/command/execute_command_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package command - -import ( - "reflect" - "testing" - - . "gopkg.in/check.v1" -) - -// Hook up gocheck into the "go test" runner. -func Test(t *testing.T) { - TestingT(t) -} - -type ExecuteCommandSuite struct{} - -var _ = Suite(&ExecuteCommandSuite{}) - -/* - * ExecuteCommand tests - * - * These are somewhat trivial right now. - */ - -func (s *ExecuteCommandSuite) Test_NewExecuteCommand(c *C) { - e := NewExecuteCommand("stmt1") - c.Assert(e, NotNil) - c.Assert(e.Stmt, Equals, "stmt1") - c.Assert(e.CommandName(), Equals, "execute") -} - -func (s *ExecuteCommandSuite) Test_NewTransactionExecuteCommandSet(c *C) { - e := NewTransactionExecuteCommandSet([]string{"stmt1"}) - c.Assert(e, NotNil) - c.Assert(reflect.DeepEqual(e.Stmts, []string{"stmt1"}), Equals, true) - c.Assert(e.CommandName(), Equals, "transaction_execute") -} diff --git a/http/service.go b/http/service.go new file mode 100644 index 00000000..c105456d --- /dev/null +++ b/http/service.go @@ -0,0 +1,121 @@ +// Package http provides the HTTP server for accessing the distributed database. +// It also provides the endpoint for other nodes to join an existing cluster. +package http + +import ( + "encoding/json" + "io/ioutil" + "log" + "net" + "net/http" + "strings" +) + +// Store is the interface the Raft-driven database must implement. +type Store interface { + // Execute executes the set of statements, possibly within a transaction. + Execute(stmts []string, tx bool) error + + // Join joins the node, reachable at addr, to the cluster. + Join(addr string) error +} + +// Service provides HTTP service. +type Service struct { + addr string + ln net.Listener + + store Store +} + +// New returns an uninitialized HTTP service. +func New(addr string, store Store) *Service { + return &Service{ + addr: addr, + store: store, + } +} + +// Start starts the service. +func (s *Service) Start() error { + server := http.Server{ + Handler: s, + } + + ln, err := net.Listen("tcp", s.addr) + if err != nil { + return err + } + s.ln = ln + + http.Handle("/", s) + + go func() { + err := server.Serve(s.ln) + if err != nil { + log.Fatalf("HTTP serve: %s", err) + } + }() + + return nil +} + +// Close closes the service. +func (s *Service) Close() { + s.ln.Close() + return +} + +// ServeHTTP allows Service to serve HTTP requests. +func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if strings.HasPrefix(r.URL.Path, "/query") { + s.handleQuery(w, r) + } else if r.URL.Path == "/join" { + s.handleJoin(w, r) + } else { + w.WriteHeader(http.StatusNotFound) + } +} + +func (s *Service) handleJoin(w http.ResponseWriter, r *http.Request) { + b, err := ioutil.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + m := map[string]string{} + if err := json.Unmarshal(b, &m); err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + if len(m) != 1 { + w.WriteHeader(http.StatusBadRequest) + return + } + + remoteAddr, ok := m["addr"] + if !ok { + w.WriteHeader(http.StatusBadRequest) + return + } + + if err := s.store.Join(remoteAddr); err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } +} + +func (s *Service) handleQuery(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + return +} + +// Addr returns the address on which the Service is listening +func (s *Service) Addr() net.Addr { + return s.ln.Addr() +} diff --git a/interfaces/interfaces.go b/interfaces/interfaces.go deleted file mode 100644 index 6b85b993..00000000 --- a/interfaces/interfaces.go +++ /dev/null @@ -1,8 +0,0 @@ -package interfaces - -import "github.com/rcrowley/go-metrics" - -// Statistics is an interface for metrics statistics -type Statistics interface { - GetStatistics() (metrics.Registry, error) -} diff --git a/log/log.go b/log/log.go deleted file mode 100644 index 2067ee37..00000000 --- a/log/log.go +++ /dev/null @@ -1,103 +0,0 @@ -package log - -import ( - "io" - "log" -) - -// Log levels -const ( - TRACE = iota // 0 - DEBUG = iota // 1 - INFO = iota // 2 - WARN = iota // 3 - ERROR = iota // 4 -) - -// Level set for logs -var Level = TRACE - -// SetLevel sets the log level -// given a string -func SetLevel(level string) { - switch level { - case "TRACE": - Level = TRACE - case "DEBUG": - Level = DEBUG - case "INFO": - Level = INFO - case "WARN": - Level = WARN - case "ERROR": - Level = ERROR - default: - Level = TRACE - } -} - -// SetOutput set the output destination -// of logs -func SetOutput(w io.Writer) { - log.SetOutput(w) -} - -// Tracef writes a formatted log on TRACE level -func Tracef(format string, v ...interface{}) { - if Level <= TRACE { - log.Printf("[TRACE] "+format, v...) - } -} - -// Trace writes a log on TRACE level -func Trace(s string) { - Tracef(s) -} - -// Debugf writes a formatted log on DEBUG level -func Debugf(format string, v ...interface{}) { - if Level <= DEBUG { - log.Printf("[DEBUG] "+format, v...) - } -} - -// Debug writes a log on DEBUG level -func Debug(s string) { - Debugf(s) -} - -// Infof writes a formatted log on INFO level -func Infof(format string, v ...interface{}) { - if Level <= INFO { - log.Printf("[INFO ] "+format, v...) - } -} - -// Info write a log on INFO level -func Info(s string) { - Infof(s) -} - -// Warnf writes a formatted log on WARN level -func Warnf(format string, v ...interface{}) { - if Level <= WARN { - log.Printf("[WARN ] "+format, v...) - } -} - -// Warn write a log on WARN level -func Warn(s string) { - Warnf(s) -} - -// Errorf writes a formatted log on ERROR level -func Errorf(format string, v ...interface{}) { - if Level <= ERROR { - log.Printf("[ERROR] "+format, v...) - } -} - -// Error write a log on ERROR level -func Error(s string) { - Errorf(s) -} diff --git a/log/log_test.go b/log/log_test.go deleted file mode 100644 index a9587432..00000000 --- a/log/log_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package log - -import ( - "bytes" - "io" - "os" - "testing" - - . "gopkg.in/check.v1" -) - -// Hook up gocheck into the "go test" runner. -func Test(t *testing.T) { - TestingT(t) -} - -type LogSuite struct{} - -var _ = Suite(&LogSuite{}) - -func (s *LogSuite) TestSetLevel(c *C) { - SetLevel("DEBUG") - c.Assert(Level, Equals, DEBUG) - - SetLevel("INFO") - c.Assert(Level, Equals, INFO) - - SetLevel("WARN") - c.Assert(Level, Equals, WARN) - - SetLevel("ERROR") - c.Assert(Level, Equals, ERROR) - - SetLevel("TRACE") - c.Assert(Level, Equals, TRACE) -} - -func (s *LogSuite) TestLog(c *C) { - logFile, err := os.OpenFile("tmptestfile.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) - c.Check(err, IsNil) - defer logFile.Close() - defer os.Remove("tmptestfile.txt") - - SetOutput(logFile) - - num, err := lineCounter(logFile) - c.Check(num, Equals, 0) - c.Check(err, IsNil) - - tmpFile, _ := os.Open("tmptestfile.txt") - Level = ERROR - Trace("a") - Debug("a") - Info("a") - Warn("a") - Error("a") - num, err = lineCounter(tmpFile) - c.Check(num, Equals, 1) - c.Check(err, IsNil) - tmpFile.Close() - - tmpFile, _ = os.Open("tmptestfile.txt") - Level = WARN - Trace("a") - Debug("a") - Info("a") - Warn("a") - Error("a") - num, err = lineCounter(tmpFile) - c.Check(num, Equals, 3) - c.Check(err, IsNil) - tmpFile.Close() - - tmpFile, _ = os.Open("tmptestfile.txt") - Level = INFO - Trace("a") - Debug("a") - Info("a") - Warn("a") - Error("a") - num, err = lineCounter(tmpFile) - c.Check(num, Equals, 6) - c.Check(err, IsNil) - tmpFile.Close() - - tmpFile, _ = os.Open("tmptestfile.txt") - Level = DEBUG - Trace("a") - Debug("a") - Info("a") - Warn("a") - Error("a") - num, err = lineCounter(tmpFile) - c.Check(num, Equals, 10) - c.Check(err, IsNil) - tmpFile.Close() - - tmpFile, _ = os.Open("tmptestfile.txt") - Level = TRACE - Trace("a") - Debug("a") - Info("a") - Warn("a") - Error("a") - num, err = lineCounter(tmpFile) - c.Check(num, Equals, 15) - c.Check(err, IsNil) - tmpFile.Close() -} - -// Taken from http://stackoverflow.com/a/24563853/1187471 -func lineCounter(r io.Reader) (int, error) { - buf := make([]byte, 8196) - count := 0 - lineSep := []byte{'\n'} - - for { - c, err := r.Read(buf) - if err != nil && err != io.EOF { - return count, err - } - - count += bytes.Count(buf[:c], lineSep) - - if err == io.EOF { - break - } - } - - return count, nil -} diff --git a/main.go b/main.go deleted file mode 100644 index e4788346..00000000 --- a/main.go +++ /dev/null @@ -1,160 +0,0 @@ -/* -rqlite -- a replicated SQLite database. - -rqlite is a distributed system that provides a replicated SQLite database. -rqlite is written in Go and uses Raft to achieve consensus across all the -instances of the SQLite databases. rqlite ensures that every change made to -the database is made to a majority of underlying SQLite files, or none-at-all. -*/ - -package main - -import ( - "bytes" - "flag" - "fmt" - "net/http" - "os" - "os/signal" - "os/user" - "path/filepath" - "runtime" - "runtime/pprof" - "strings" - "time" - - "github.com/otoolep/rqlite/log" - "github.com/otoolep/rqlite/server" -) - -var host string -var port int -var join string -var dbfile string -var cpuprofile string -var logFile string -var logLevel string -var snapAfter int -var disableReporting bool - -func init() { - flag.StringVar(&host, "h", "localhost", "hostname") - flag.IntVar(&port, "p", 4001, "port") - flag.StringVar(&join, "join", "", "host:port of leader to join") - flag.StringVar(&dbfile, "dbfile", "db.sqlite", "sqlite filename") - flag.StringVar(&cpuprofile, "cpuprofile", "", "write CPU profile to file") - flag.StringVar(&logFile, "logfile", "stdout", "log file path") - flag.StringVar(&logLevel, "loglevel", "INFO", "log level (ERROR|WARN|INFO|DEBUG|TRACE)") - flag.IntVar(&snapAfter, "s", 100, "Snapshot and compact after this number of new log entries") - flag.BoolVar(&disableReporting, "noreport", false, "Disable anonymised launch reporting") - flag.Usage = func() { - fmt.Fprintf(os.Stderr, "Usage: %s [arguments] \n", os.Args[0]) - flag.PrintDefaults() - } -} - -func main() { - flag.Parse() - - // Set up profiling, if requested. - if cpuprofile != "" { - log.Info("Profiling enabled") - f, err := os.Create(cpuprofile) - if err != nil { - log.Errorf("Unable to create path: %s", err.Error()) - } - defer closeFile(f) - - err = pprof.StartCPUProfile(f) - if err != nil { - log.Errorf("Unable to start CPU Profile: %s", err.Error()) - } - - defer pprof.StopCPUProfile() - } - - // Set logging - log.SetLevel(logLevel) - if logFile != "stdout" { - f := createFile(logFile) - defer closeFile(f) - - log.Infof("Redirecting logging to %s", logFile) - log.SetOutput(f) - } - - // Set the data directory. - if flag.NArg() == 0 { - flag.Usage() - println("Data path argument required") - log.Error("No data path supplied -- aborting") - os.Exit(1) - } - - dataPath := flag.Arg(0) - createDir(dataPath) - - s := server.NewServer(dataPath, dbfile, snapAfter, host, port) - go func() { - log.Error(s.ListenAndServe(join).Error()) - }() - - if !disableReporting { - reportLaunch() - } - - terminate := make(chan os.Signal, 1) - signal.Notify(terminate, os.Interrupt) - <-terminate - log.Info("rqlite server stopped") -} - -func closeFile(f *os.File) { - if err := f.Close(); err != nil { - log.Errorf("Unable to close file: %s", err.Error()) - os.Exit(1) - } -} - -func createFile(path string) *os.File { - usr, _ := user.Current() - dir := usr.HomeDir - - // Check in case of paths like "/something/~/something/" - if path[:2] == "~/" { - path = strings.Replace(path, "~/", dir+"/", 1) - } - - if err := os.MkdirAll(filepath.Dir(path), 0744); err != nil { - log.Errorf("Unable to create path: %s", err.Error()) - os.Exit(1) - } - - f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) - if err != nil && !strings.Contains(err.Error(), "is a directory") { - log.Errorf("Unable to open file: %s", err.Error()) - os.Exit(1) - } - - return f -} - -func createDir(path string) { - if err := os.MkdirAll(path, 0744); err != nil { - log.Errorf("Unable to create path: %s", err.Error()) - os.Exit(1) - } -} - -func reportLaunch() { - json := fmt.Sprintf(`{"os": "%s", "arch": "%s", "app": "rqlite"}`, runtime.GOOS, runtime.GOARCH) - data := bytes.NewBufferString(json) - client := http.Client{Timeout: time.Duration(5 * time.Second)} - go func() { - _, err := client.Post("https://logs-01.loggly.com/inputs/8a0edd84-92ba-46e4-ada8-c529d0f105af/tag/reporting/", - "application/json", data) - if err != nil { - log.Errorf("Report launch failed: %s", err.Error()) - } - }() -} diff --git a/server/server.go b/server/server.go deleted file mode 100644 index 42c91042..00000000 --- a/server/server.go +++ /dev/null @@ -1,633 +0,0 @@ -package server - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "math/rand" - "net/http" - "net/url" - "os" - "path" - "path/filepath" - "strings" - "sync" - "time" - - "github.com/gorilla/mux" - "github.com/otoolep/raft" - "github.com/otoolep/rqlite/command" - "github.com/otoolep/rqlite/db" - "github.com/otoolep/rqlite/interfaces" - "github.com/otoolep/rqlite/log" - "github.com/rcrowley/go-metrics" -) - -// FailedSqlStmt contains a SQL query and an error. -type FailedSqlStmt struct { - Sql string `json:"sql"` - Error string `json:"error"` -} - -// StmtResponse contains a date and a list of failed -// SQL statements. -type StmtResponse struct { - Time string `json:"time,omitempty"` - Failures []FailedSqlStmt `json:"failures"` -} - -// QueryResponse contains the response to a query. -type QueryResponse struct { - Time string `json:"time,omitempty"` - Failures []FailedSqlStmt `json:"failures"` - Rows db.RowResults `json:"rows"` -} - -// Metrics are the server metrics user for statistics. -type Metrics struct { - registry metrics.Registry - joinSuccess metrics.Counter - joinFail metrics.Counter - queryReceived metrics.Counter - querySuccess metrics.Counter - queryFail metrics.Counter - executeReceived metrics.Counter - executeTxReceived metrics.Counter - executeSuccess metrics.Counter - executeFail metrics.Counter - snapshotCreated metrics.Counter -} - -// Diagnostics contains a start time of the server. -type Diagnostics struct { - startTime time.Time -} - -// SnapshotConf contains the index when the last snapshot happened -// and a threshold for index entries since the last snapshot. -type SnapshotConf struct { - // The index when the last snapshot happened - lastIndex uint64 - - // If the incremental number of index entries since the last - // snapshot exceeds snapshotAfter rqlite will do a snapshot - snapshotAfter uint64 -} - -// Server is is a combination of the Raft server and an HTTP -// server which acts as the transport. -type Server struct { - name string - host string - port int - path string - router *mux.Router - raftServer raft.Server - httpServer *http.Server - dbPath string - db *db.DB - snapConf *SnapshotConf - metrics *Metrics - diagnostics *Diagnostics - mutex sync.Mutex -} - -// ensurePrettyPrint returns a JSON representation of the object o. If -// the HTTP request requested pretty-printing, it ensures that happens. -func ensurePrettyPrint(req *http.Request, o map[string]interface{}) []byte { - var b []byte - pretty, _ := isPretty(req) - if pretty { - b, _ = json.MarshalIndent(o, "", " ") - } else { - b, _ = json.Marshal(o) - } - return b -} - -// queryParam returns whether the given query param is set to true. -func queryParam(req *http.Request, param string) (bool, error) { - err := req.ParseForm() - if err != nil { - return false, err - } - if _, ok := req.Form[param]; ok { - return true, nil - } - return false, nil -} - -// stmtParam returns the value for URL param 'q', if present. -func stmtParam(req *http.Request) (string, error) { - q := req.URL.Query() - stmt := strings.TrimSpace(q.Get("q")) - if stmt == "" { - return "", fmt.Errorf(`required parameter 'q' is missing`) - } - return stmt, nil -} - -// isPretty returns whether the HTTP response body should be pretty-printed. -func isPretty(req *http.Request) (bool, error) { - return queryParam(req, "pretty") -} - -// isExplain returns whether the HTTP response body should contain metainformation -// how request processing. -func isExplain(req *http.Request) (bool, error) { - return queryParam(req, "explain") -} - -// isTransaction returns whether the client requested an explicit -// transaction for the request. -func isTransaction(req *http.Request) (bool, error) { - return queryParam(req, "transaction") -} - -// NewMetrics creates a new Metrics object. -func NewMetrics() *Metrics { - m := &Metrics{ - registry: metrics.NewRegistry(), - joinSuccess: metrics.NewCounter(), - joinFail: metrics.NewCounter(), - queryReceived: metrics.NewCounter(), - querySuccess: metrics.NewCounter(), - queryFail: metrics.NewCounter(), - executeReceived: metrics.NewCounter(), - executeTxReceived: metrics.NewCounter(), - executeSuccess: metrics.NewCounter(), - executeFail: metrics.NewCounter(), - snapshotCreated: metrics.NewCounter(), - } - - _ = m.registry.Register("join_success", m.joinSuccess) - _ = m.registry.Register("join_fail", m.joinFail) - _ = m.registry.Register("query_received", m.queryReceived) - _ = m.registry.Register("query_success", m.querySuccess) - _ = m.registry.Register("query_fail", m.queryFail) - _ = m.registry.Register("execute_received", m.executeReceived) - _ = m.registry.Register("execute_tx_received", m.executeTxReceived) - _ = m.registry.Register("execute_success", m.executeSuccess) - _ = m.registry.Register("execute_fail", m.executeFail) - _ = m.registry.Register("snapshot_created", m.snapshotCreated) - - return m -} - -// NewDiagnostics creates a new Diagnostics object. -func NewDiagnostics() *Diagnostics { - d := &Diagnostics{ - startTime: time.Now(), - } - return d -} - -// NewServer creates a new server. -func NewServer(dataDir string, dbfile string, snapAfter int, host string, port int) *Server { - dbPath := path.Join(dataDir, dbfile) - - // Raft requires randomness. - rand.Seed(time.Now().UnixNano()) - log.Info("Raft random seed initialized") - - // Setup commands. - raft.RegisterCommand(&command.ExecuteCommand{}) - raft.RegisterCommand(&command.TransactionExecuteCommandSet{}) - log.Info("Raft commands registered") - - s := &Server{ - host: host, - port: port, - path: dataDir, - dbPath: dbPath, - db: db.New(dbPath), - snapConf: &SnapshotConf{snapshotAfter: uint64(snapAfter)}, - metrics: NewMetrics(), - diagnostics: NewDiagnostics(), - router: mux.NewRouter(), - } - - // Read existing name or generate a new one. - if b, err := ioutil.ReadFile(filepath.Join(dataDir, "name")); err == nil { - s.name = string(b) - } else { - s.name = fmt.Sprintf("%07x", rand.Int())[0:7] - if err = ioutil.WriteFile(filepath.Join(dataDir, "name"), []byte(s.name), 0644); err != nil { - panic(err) - } - } - - return s -} - -// GetStatistics returns an object storing statistics, which supports JSON -// marshalling. -func (s *Server) GetStatistics() (metrics.Registry, error) { - return s.metrics.registry, nil -} - -// connectionString returns the string used to connect to this server. -func (s *Server) connectionString() string { - return fmt.Sprintf("http://%s:%d", s.host, s.port) -} - -// logSnapshot logs about the snapshot that was taken. -func (s *Server) logSnapshot(err error, currentIndex, count uint64) { - info := fmt.Sprintf("%s: snapshot of %d events at index %d", s.connectionString(), count, currentIndex) - if err != nil { - log.Infof("%s attempted and failed: %v", info, err) - } else { - log.Infof("%s completed", info) - } -} - -// ListenAndServe starts the server. -func (s *Server) ListenAndServe(leader string) error { - var err error - - log.Infof("Initializing Raft Server: %s", s.path) - - // Initialize and start Raft server. - transporter := raft.NewHTTPTransporter("/raft", 200*time.Millisecond) - stateMachine := NewDbStateMachine(s.dbPath) - s.raftServer, err = raft.NewServer(s.name, s.path, transporter, stateMachine, s.db, "") - if err != nil { - log.Errorf("Failed to create new Raft server: %s", err.Error()) - return err - } - - log.Info("Loading latest snapshot, if any, from disk") - if err := s.raftServer.LoadSnapshot(); err != nil && os.IsNotExist(err) { - log.Info("no snapshot found") - } else if err != nil { - log.Errorf("Error loading snapshot: %s", err.Error()) - } - - transporter.Install(s.raftServer, s) - if err := s.raftServer.Start(); err != nil { - log.Errorf("Error starting raft server: %s", err.Error()) - } - - if leader != "" { - // Join to leader if specified. - - log.Infof("Attempting to join leader at %s", leader) - - if !s.raftServer.IsLogEmpty() { - log.Error("Cannot join with an existing log") - return errors.New("Cannot join with an existing log") - } - if err := s.Join(leader); err != nil { - log.Errorf("Failed to join leader: %s", err.Error()) - return err - } - - } else if s.raftServer.IsLogEmpty() { - // Initialize the server by joining itself. - - log.Info("Initializing new cluster") - - _, err := s.raftServer.Do(&raft.DefaultJoinCommand{ - Name: s.raftServer.Name(), - ConnectionString: s.connectionString(), - }) - if err != nil { - log.Errorf("Failed to join to self: %s", err.Error()) - } - - } else { - log.Info("Recovered from log") - } - - log.Info("Initializing HTTP server") - - // Initialize and start HTTP server. - s.httpServer = &http.Server{ - Addr: fmt.Sprintf(":%d", s.port), - Handler: s.router, - } - - s.router.HandleFunc("/statistics", s.serveStatistics).Methods("GET") - s.router.HandleFunc("/diagnostics", s.serveDiagnostics).Methods("GET") - s.router.HandleFunc("/raft", s.serveRaftInfo).Methods("GET") - s.router.HandleFunc("/db", s.readHandler).Methods("GET") - s.router.HandleFunc("/db", s.writeHandler).Methods("POST") - s.router.HandleFunc("/join", s.joinHandler).Methods("POST") - - log.Infof("Listening at %s", s.connectionString()) - - return s.httpServer.ListenAndServe() -} - -// HandleFunc is a hack around Gorilla mux not providing the correct net/http -// HandleFunc() interface. -func (s *Server) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) { - s.router.HandleFunc(pattern, handler) -} - -// Join joins to the leader of an existing cluster. -func (s *Server) Join(leader string) error { - command := &raft.DefaultJoinCommand{ - Name: s.raftServer.Name(), - ConnectionString: s.connectionString(), - } - - var b bytes.Buffer - if err := json.NewEncoder(&b).Encode(command); err != nil { - return nil - } - - resp, err := http.Post(fmt.Sprintf("http://%s/join", leader), "application/json", &b) - if err != nil { - return err - } - defer func() { - _ = resp.Body.Close() - }() - - // Look for redirect. - if resp.StatusCode == http.StatusTemporaryRedirect { - leader := resp.Header.Get("Location") - if leader == "" { - return errors.New("Redirect requested, but no location header supplied") - } - u, err := url.Parse(leader) - if err != nil { - return errors.New("Failed to parse redirect location") - } - log.Infof("Redirecting to leader at %s", u.Host) - return s.Join(u.Host) - } - - return nil -} - -func (s *Server) joinHandler(w http.ResponseWriter, req *http.Request) { - s.mutex.Lock() - defer s.mutex.Unlock() - - if s.raftServer.State() != "leader" { - s.leaderRedirect(w, req) - return - } - - command := &raft.DefaultJoinCommand{} - - if err := json.NewDecoder(req.Body).Decode(&command); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - s.metrics.joinFail.Inc(1) - return - } - if _, err := s.raftServer.Do(command); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - s.metrics.joinFail.Inc(1) - return - } - s.metrics.joinSuccess.Inc(1) -} - -func (s *Server) readHandler(w http.ResponseWriter, req *http.Request) { - log.Tracef("readHandler for URL: %s", req.URL) - s.metrics.queryReceived.Inc(1) - - var failures = make([]FailedSqlStmt, 0) - - // Get the query statement - stmt, err := stmtParam(req) - if err != nil { - log.Tracef("Bad HTTP request: %s", err.Error()) - w.WriteHeader(http.StatusBadRequest) - s.metrics.queryFail.Inc(1) - return - } - - startTime := time.Now() - r, err := s.db.Query(stmt) - if err != nil { - log.Tracef("Bad SQL statement: %s", err.Error()) - s.metrics.queryFail.Inc(1) - failures = append(failures, FailedSqlStmt{stmt, err.Error()}) - } else { - s.metrics.querySuccess.Inc(1) - } - duration := time.Since(startTime) - - rr := QueryResponse{Failures: failures, Rows: r} - if e, _ := isExplain(req); e { - rr.Time = duration.String() - } - - pretty, _ := isPretty(req) - var b []byte - if pretty { - b, err = json.MarshalIndent(rr, "", " ") - } else { - b, err = json.Marshal(rr) - } - if err != nil { - log.Tracef("Failed to marshal JSON data: %s", err.Error()) - http.Error(w, err.Error(), http.StatusBadRequest) // Internal error actually - } else { - _, err = w.Write([]byte(b)) - if err != nil { - log.Errorf("Error writting JSON data: %s", err.Error()) - } - } -} - -func (s *Server) execute(tx bool, stmts []string) ([]FailedSqlStmt, error) { - var failures = make([]FailedSqlStmt, 0) - - if tx { - log.Trace("Transaction requested") - s.metrics.executeTxReceived.Inc(1) - - _, err := s.raftServer.Do(command.NewTransactionExecuteCommandSet(stmts)) - if err != nil { - log.Tracef("Transaction failed: %s", err.Error()) - s.metrics.executeFail.Inc(1) - failures = append(failures, FailedSqlStmt{stmts[0], err.Error()}) - } else { - s.metrics.executeSuccess.Inc(1) - } - } else { - log.Trace("No transaction requested") - for i := range stmts { - _, err := s.raftServer.Do(command.NewExecuteCommand(stmts[i])) - if err != nil { - log.Tracef("Execute statement %s failed: %s", stmts[i], err.Error()) - s.metrics.executeFail.Inc(1) - failures = append(failures, FailedSqlStmt{stmts[i], err.Error()}) - } else { - s.metrics.executeSuccess.Inc(1) - } - - } - } - - return failures, nil -} - -func (s *Server) writeHandler(w http.ResponseWriter, req *http.Request) { - s.mutex.Lock() - defer s.mutex.Unlock() - - if s.raftServer.State() != "leader" { - s.leaderRedirect(w, req) - return - } - - log.Tracef("writeHandler for URL: %s", req.URL) - s.metrics.executeReceived.Inc(1) - - currentIndex := s.raftServer.CommitIndex() - count := currentIndex - s.snapConf.lastIndex - if uint64(count) > s.snapConf.snapshotAfter { - log.Info("Committed log entries snapshot threshold reached, starting snapshot") - err := s.raftServer.TakeSnapshot() - s.logSnapshot(err, currentIndex, count) - s.snapConf.lastIndex = currentIndex - s.metrics.snapshotCreated.Inc(1) - } - - // Read the value from the POST body. - b, err := ioutil.ReadAll(req.Body) - if err != nil { - log.Tracef("Bad HTTP request: %s", err.Error()) - s.metrics.executeFail.Inc(1) - w.WriteHeader(http.StatusBadRequest) - return - } - stmts := strings.Split(string(b), ";") - if stmts[len(stmts)-1] == "" { - stmts = stmts[:len(stmts)-1] - } - - log.Tracef("Execute statement contains %d commands", len(stmts)) - if len(stmts) == 0 { - log.Trace("No database execute commands supplied") - s.metrics.executeFail.Inc(1) - w.WriteHeader(http.StatusBadRequest) - return - } - - transaction, _ := isTransaction(req) - startTime := time.Now() - failures, err := s.execute(transaction, stmts) - if err != nil { - log.Errorf("Database mutation failed: %s", err.Error()) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - duration := time.Since(startTime) - - wr := StmtResponse{Failures: failures} - if e, _ := isExplain(req); e { - wr.Time = duration.String() - } - - pretty, _ := isPretty(req) - if pretty { - b, err = json.MarshalIndent(wr, "", " ") - } else { - b, err = json.Marshal(wr) - } - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - _, err = w.Write([]byte(b)) - if err != nil { - log.Errorf("Error writting JSON data: %s", err.Error()) - } - } -} - -// serveStatistics returns the statistics for the program -func (s *Server) serveStatistics(w http.ResponseWriter, req *http.Request) { - statistics := make(map[string]interface{}) - resources := map[string]interfaces.Statistics{"server": s} - for k, v := range resources { - s, err := v.GetStatistics() - if err != nil { - log.Error("failed to get " + k + " stats") - http.Error(w, "failed to get "+k+" stats", http.StatusInternalServerError) - return - } - statistics[k] = s - } - - _, err := w.Write(ensurePrettyPrint(req, statistics)) - if err != nil { - log.Error("failed to serve stats") - http.Error(w, "failed to serve stats", http.StatusInternalServerError) - return - } -} - -// serveDiagnostics returns basic server diagnostics -func (s *Server) serveDiagnostics(w http.ResponseWriter, req *http.Request) { - diagnostics := make(map[string]interface{}) - diagnostics["started"] = s.diagnostics.startTime.String() - diagnostics["uptime"] = time.Since(s.diagnostics.startTime).String() - diagnostics["host"] = s.host - diagnostics["port"] = s.port - diagnostics["data"] = s.path - diagnostics["database"] = s.dbPath - diagnostics["connection"] = s.connectionString() - diagnostics["snapafter"] = s.snapConf.snapshotAfter - diagnostics["snapindex"] = s.snapConf.lastIndex - - _, err := w.Write(ensurePrettyPrint(req, diagnostics)) - if err != nil { - log.Error("failed to serve diagnostics") - http.Error(w, "failed to serve diagnostics", http.StatusInternalServerError) - return - } -} - -// serveRaftInfo returns information about the underlying Raft server -func (s *Server) serveRaftInfo(w http.ResponseWriter, req *http.Request) { - var peers []interface{} - for _, v := range s.raftServer.Peers() { - peers = append(peers, v) - } - - info := make(map[string]interface{}) - info["name"] = s.raftServer.Name() - info["state"] = s.raftServer.State() - info["leader"] = s.raftServer.Leader() - info["peers"] = peers - - _, err := w.Write(ensurePrettyPrint(req, info)) - if err != nil { - log.Error("failed to serve raft info") - http.Error(w, "failed to serve raft info", http.StatusInternalServerError) - return - } -} - -// leaderRedirect returns a 307 Temporary Redirect, with the full path -// to the leader. -func (s *Server) leaderRedirect(w http.ResponseWriter, r *http.Request) { - peers := s.raftServer.Peers() - leader := peers[s.raftServer.Leader()] - - if leader == nil { - // No leader available, give up. - log.Error("attempted leader redirection, but no leader available") - w.WriteHeader(http.StatusServiceUnavailable) - w.Write([]byte("no leader available")) - return - } - - var u string - for _, p := range peers { - if p.Name == leader.Name { - u = p.ConnectionString - break - } - } - http.Redirect(w, r, u+r.URL.Path, http.StatusTemporaryRedirect) -} diff --git a/server/single_server_test.go b/server/single_server_test.go deleted file mode 100644 index f05946dc..00000000 --- a/server/single_server_test.go +++ /dev/null @@ -1,117 +0,0 @@ -package server - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "os" - "time" - - . "gopkg.in/check.v1" -) - -const ( - host = "localhost" - port = 4001 - snapAfter = 1000 - dbfile = "rqlite-test" -) - -type SingleServerSuite struct{} - -var _ = Suite(&SingleServerSuite{}) - -func getEndpoint(endpoint string) (*http.Response, error) { - url := fmt.Sprintf("http://%s:%d%s", host, port, endpoint) - return http.Get(url) -} - -func getEndpointQuery(endpoint string, query string) (*http.Response, error) { - q := url.Values{"q": []string{query}} - v, _ := url.Parse(fmt.Sprintf("http://%s:%d%s", host, port, endpoint)) - v.RawQuery = q.Encode() - - req, err := http.Get(v.String()) - if err != nil { - panic(err) - } - return req, nil -} - -func postEndpoint(endpoint string, body string) (*http.Response, error) { - var jsonStr = []byte(body) - url := fmt.Sprintf("http://%s:%d%s", host, port, endpoint) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr)) - if err != nil { - panic(err) - } - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - return client.Do(req) -} - -func isJSONBody(res *http.Response) bool { - b, err := ioutil.ReadAll(res.Body) - if err != nil { - return false - } - - var o interface{} - err = json.Unmarshal(b, &o) - if err != nil { - return false - } - - return true -} - -func (s *SingleServerSuite) Test_SingleServer(c *C) { - dir, err := ioutil.TempDir("", "rqlite-test-") - c.Assert(err, IsNil) - defer os.RemoveAll(dir) - - server := NewServer(dir, dbfile, snapAfter, host, port) - c.Assert(server, NotNil) - go func() { server.ListenAndServe("") }() - - // Wait to ensure server is up. This is not ideal, and the server should - // really use a channel to flag it is ready. - time.Sleep(1 * time.Second) - - // Sanity-check admin API endpoints - var res *http.Response - res, err = getEndpoint("/statistics") - c.Assert(err, IsNil) - c.Assert(res.StatusCode, Equals, 200) - c.Assert(isJSONBody(res), Equals, true) - - res, err = getEndpoint("/diagnostics") - c.Assert(err, IsNil) - c.Assert(res.StatusCode, Equals, 200) - c.Assert(isJSONBody(res), Equals, true) - - res, err = getEndpoint("/raft") - c.Assert(err, IsNil) - c.Assert(res.StatusCode, Equals, 200) - c.Assert(isJSONBody(res), Equals, true) - - // Create a database. - res, err = postEndpoint("/db", "CREATE TABLE foo (id integer not null primary key, name text)") - c.Assert(err, IsNil) - c.Assert(res.StatusCode, Equals, 200) - - // Data write. - res, err = postEndpoint("/db", "INSERT INTO foo(name) VALUES(\"fiona\")") - c.Assert(err, IsNil) - c.Assert(res.StatusCode, Equals, 200) - - // Data read - res, err = getEndpointQuery("/db", "SELECT * from foo") - c.Assert(err, IsNil) - c.Assert(res.StatusCode, Equals, 200) - c.Assert(isJSONBody(res), Equals, true) -} diff --git a/server/statemachine.go b/server/statemachine.go deleted file mode 100644 index 5f2586b8..00000000 --- a/server/statemachine.go +++ /dev/null @@ -1,51 +0,0 @@ -package server - -import ( - "io/ioutil" - "os" - - "github.com/otoolep/rqlite/log" -) - -// DbStateMachine contains the DB path. -type DbStateMachine struct { - dbpath string -} - -// NewDbStateMachine returns a StateMachine for capturing and restoring -// the state of an sqlite database. -func NewDbStateMachine(path string) *DbStateMachine { - d := &DbStateMachine{ - dbpath: path, - } - log.Tracef("New DB state machine created with path: %s", path) - return d -} - -// Save captures the state of the database. The caller must ensure that -// no transaction is taking place during this call. -// -// http://sqlite.org/howtocorrupt.html states it is safe to do this -// as long as no transaction is in progress. -func (d *DbStateMachine) Save() ([]byte, error) { - log.Tracef("Capturing database state from path: %s", d.dbpath) - b, err := ioutil.ReadFile(d.dbpath) - if err != nil { - log.Errorf("Failed to save state: %s", err.Error()) - return nil, err - } - log.Tracef("Database state successfully saved to %s", d.dbpath) - return b, nil -} - -// Recovery restores the state of the database using the given data. -func (d *DbStateMachine) Recovery(b []byte) error { - log.Tracef("Restoring database state to path: %s", d.dbpath) - err := ioutil.WriteFile(d.dbpath, b, os.ModePerm) - if err != nil { - log.Errorf("Failed to recover state: %s", err.Error()) - return err - } - log.Tracef("Database restored successfully to %s", d.dbpath) - return nil -} diff --git a/server/statemachine_test.go b/server/statemachine_test.go deleted file mode 100644 index 3baba8ce..00000000 --- a/server/statemachine_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package server - -import ( - "database/sql" - "io/ioutil" - "os" - "path" - "reflect" - "testing" - - _ "github.com/mattn/go-sqlite3" - . "gopkg.in/check.v1" -) - -// Hook up gocheck into the "go test" runner. -func Test(t *testing.T) { - TestingT(t) -} - -type SnapshotSuite struct{} - -var _ = Suite(&SnapshotSuite{}) - -func (s *SnapshotSuite) Test_Snapshot(c *C) { - dir, err := ioutil.TempDir("", "rqlite-test-") - path1 := path.Join(dir, "test_db1") - path2 := path.Join(dir, "test_db2") - defer os.RemoveAll(dir) - - // Create a small database. - dbc, err := sql.Open("sqlite3", path1) - c.Assert(err, IsNil) - _, err = dbc.Exec("create table foo (id integer not null primary key, name text)") - c.Assert(err, IsNil) - _, err = dbc.Exec("INSERT INTO foo(name) VALUES(\"fiona\")") - dbc.Close() - - // Snapshot it. - snapper1 := NewDbStateMachine(path1) - c.Assert(snapper1, NotNil) - snap, err := snapper1.Save() - c.Assert(err, IsNil) - - // Save it to a different location. - snapper2 := NewDbStateMachine(path2) - c.Assert(snapper2, NotNil) - err = snapper2.Recovery(snap) - c.Assert(err, IsNil) - - // Confirm two files are byte-for-byte identical. - b1, err := ioutil.ReadFile(path1) - c.Assert(err, IsNil) - b2, err := ioutil.ReadFile(path2) - c.Assert(err, IsNil) - c.Assert(reflect.DeepEqual(b1, b2), Equals, true) - - // Open database using snapshot copy. - dbc, err = sql.Open("sqlite3", path2) - c.Assert(err, IsNil) - rows, err := dbc.Query("SELECT name FROM foo") - c.Assert(err, IsNil) - - var nrows int - for rows.Next() { - var name string - err = rows.Scan(&name) - c.Assert(err, IsNil) - c.Assert(name, Equals, "fiona") - nrows++ - } - c.Assert(nrows, Equals, 1) - dbc.Close() -} diff --git a/store/store.go b/store/store.go new file mode 100644 index 00000000..9dc3f899 --- /dev/null +++ b/store/store.go @@ -0,0 +1,192 @@ +// Package store provides a distributed SQLite instance. +// +// Distributed consensus is provided via the Raft algorithm. +package store + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "net" + "os" + "path/filepath" + "sync" + "time" + + "github.com/hashicorp/raft" + "github.com/hashicorp/raft-boltdb" +) + +const ( + retainSnapshotCount = 2 + raftTimeout = 10 * time.Second +) + +// Store is a SQLite database, where all changes are made via Raft consensus. +type Store struct { + raftDir string + raftBind string + + mu sync.Mutex + + raft *raft.Raft // The consensus mechanism + + logger *log.Logger +} + +// New returns a new Store. +func New(dir, bind string) *Store { + return &Store{ + raftDir: dir, + raftBind: bind, + logger: log.New(os.Stderr, "[store] ", log.LstdFlags), + } +} + +// Open opens the store. If enableSingle is set, and there are no existing peers, +// then this node becomesthe first node, and therefore leader, of the cluster. +func (s *Store) Open(enableSingle bool) error { + // Setup Raft configuration. + config := raft.DefaultConfig() + + // Check for any existing peers. + peers, err := readPeersJSON(filepath.Join(s.raftDir, "peers.json")) + if err != nil { + return err + } + + // Allow the node to entry single-mode, potentially electing itself, if + // explicitly enabled and there is only 1 node in the cluster already. + if enableSingle && len(peers) <= 1 { + s.logger.Println("enabling single-node mode") + config.EnableSingleNode = true + config.DisableBootstrapAfterElect = false + } + + // Setup Raft communication. + addr, err := net.ResolveTCPAddr("tcp", s.raftBind) + if err != nil { + return err + } + transport, err := raft.NewTCPTransport(s.raftBind, addr, 3, 10*time.Second, os.Stderr) + if err != nil { + return err + } + + // Create peer storage. + peerStore := raft.NewJSONPeers(s.raftDir, transport) + + // Create the snapshot store. This allows the Raft to truncate the log. + snapshots, err := raft.NewFileSnapshotStore(s.raftDir, retainSnapshotCount, os.Stderr) + if err != nil { + return fmt.Errorf("file snapshot store: %s", err) + } + + // Create the log store and stable store. + logStore, err := raftboltdb.NewBoltStore(filepath.Join(s.raftDir, "raft.db")) + if err != nil { + return fmt.Errorf("new bolt store: %s", err) + } + + // Instantiate the Raft systems. + ra, err := raft.NewRaft(config, (*fsm)(s), logStore, logStore, snapshots, peerStore, transport) + if err != nil { + return fmt.Errorf("new raft: %s", err) + } + s.raft = ra + return nil +} + +func (s *Store) Execute(stmts []string, tx bool) error { + return nil +} + +// Join joins a node, located at addr, to this store. The node must be ready to +// respond to Raft communications at that address. +func (s *Store) Join(addr string) error { + s.logger.Printf("received join request for remote node as %s", addr) + + f := s.raft.AddPeer(addr) + if f.Error() != nil { + return f.Error() + } + s.logger.Printf("node at %s joined successfully", addr) + return nil +} + +type fsm Store + +// Apply applies a Raft log entry to the database. +func (f *fsm) Apply(l *raft.Log) interface{} { + return nil +} + +// Snapshot returns a snapshot of the database. +func (f *fsm) Snapshot() (raft.FSMSnapshot, error) { + f.mu.Lock() + defer f.mu.Unlock() + + return nil, nil +} + +// Restore restores the database to a previous state. +func (f *fsm) Restore(rc io.ReadCloser) error { + return nil +} + +type fsmSnapshot struct { + store map[string]string +} + +func (f *fsmSnapshot) Persist(sink raft.SnapshotSink) error { + err := func() error { + // Encode data. + b, err := json.Marshal(f.store) + if err != nil { + return err + } + + // Write data to sink. + if _, err := sink.Write(b); err != nil { + return err + } + + // Close the sink. + if err := sink.Close(); err != nil { + return err + } + + return nil + }() + + if err != nil { + sink.Cancel() + return err + } + + return nil +} + +func (f *fsmSnapshot) Release() {} + +func readPeersJSON(path string) ([]string, error) { + b, err := ioutil.ReadFile(path) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + + if len(b) == 0 { + return nil, nil + } + + var peers []string + dec := json.NewDecoder(bytes.NewReader(b)) + if err := dec.Decode(&peers); err != nil { + return nil, err + } + + return peers, nil +} From 2573360320c20388a246597911d753244c18d24c Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Sat, 13 Feb 2016 22:07:48 -0800 Subject: [PATCH 02/12] Start integrating actual SQLite support Builds without error. --- cmd/rqlited/main.go | 2 +- db/db.go | 42 +----------------------------------------- store/store.go | 4 ++++ 3 files changed, 6 insertions(+), 42 deletions(-) diff --git a/cmd/rqlited/main.go b/cmd/rqlited/main.go index cfac4ac3..0614d749 100644 --- a/cmd/rqlited/main.go +++ b/cmd/rqlited/main.go @@ -101,7 +101,7 @@ func reportLaunch() { _, err := client.Post("https://logs-01.loggly.com/inputs/8a0edd84-92ba-46e4-ada8-c529d0f105af/tag/reporting/", "application/json", data) if err != nil { - log.Printf("Report launch failed: %s", err.Error()) + log.Printf("report launch failed: %s", err.Error()) } }() } diff --git a/db/db.go b/db/db.go index d33e49f0..1aadaa93 100644 --- a/db/db.go +++ b/db/db.go @@ -2,12 +2,9 @@ package db import ( "database/sql" - "fmt" "os" - "strings" _ "github.com/mattn/go-sqlite3" // required blank import - "github.com/otoolep/rqlite/log" ) // DB is the SQL database. @@ -23,17 +20,14 @@ type RowResults []map[string]string // New creates a new database. Deletes any existing database. func New(dbPath string) *DB { - log.Tracef("Removing any existing SQLite database at %s", dbPath) _ = os.Remove(dbPath) return Open(dbPath) } // Open an existing database, creating it if it does not exist. func Open(dbPath string) *DB { - log.Tracef("Opening SQLite database path at %s", dbPath) dbc, err := sql.Open("sqlite3", dbPath) if err != nil { - log.Error(err.Error()) return nil } return &DB{ @@ -49,19 +43,12 @@ func (db *DB) Close() error { // Query runs the supplied query against the sqlite database. It returns a slice of // RowResults. func (db *DB) Query(query string) (RowResults, error) { - if !strings.HasPrefix(strings.ToUpper(query), "SELECT ") { - log.Warnf("Query \"%s\" may modify the database", query) - } rows, err := db.dbConn.Query(query) if err != nil { - log.Errorf("failed to execute SQLite query: %s", err.Error()) return nil, err } defer func() { - err = rows.Close() - if err != nil { - log.Errorf("failed to close rows: %s", err.Error()) - } + rows.Close() }() results := make(RowResults, 0) @@ -76,7 +63,6 @@ func (db *DB) Query(query string) (RowResults, error) { for rows.Next() { err = rows.Scan(dest...) if err != nil { - log.Errorf("failed to scan SQLite row: %s", err.Error()) return nil, err } @@ -90,44 +76,24 @@ func (db *DB) Query(query string) (RowResults, error) { } results = append(results, r) } - log.Debugf("Executed query successfully: %s", query) return results, nil } // Execute executes the given sqlite statement, of a type that doesn't return rows. func (db *DB) Execute(stmt string) error { _, err := db.dbConn.Exec(stmt) - log.Debug(func() string { - if err != nil { - return fmt.Sprintf("Error executing \"%s\", error: %s", stmt, err.Error()) - } - return fmt.Sprintf("Successfully executed \"%s\"", stmt) - }()) - return err } // StartTransaction starts an explicit transaction. func (db *DB) StartTransaction() error { _, err := db.dbConn.Exec("BEGIN") - log.Debug(func() string { - if err != nil { - return "Error starting transaction" - } - return "Successfully started transaction" - }()) return err } // CommitTransaction commits all changes made since StartTraction was called. func (db *DB) CommitTransaction() error { _, err := db.dbConn.Exec("END") - log.Debug(func() string { - if err != nil { - return "Error ending transaction" - } - return "Successfully ended transaction" - }()) return err } @@ -135,11 +101,5 @@ func (db *DB) CommitTransaction() error { // StartTransaction was called will take effect. func (db *DB) RollbackTransaction() error { _, err := db.dbConn.Exec("ROLLBACK") - log.Debug(func() string { - if err != nil { - return "Error rolling back transaction" - } - return "Successfully rolled back transaction" - }()) return err } diff --git a/store/store.go b/store/store.go index 9dc3f899..f074e4ef 100644 --- a/store/store.go +++ b/store/store.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/raft" "github.com/hashicorp/raft-boltdb" + "github.com/otoolep/rqlite/db" ) const ( @@ -97,6 +98,9 @@ func (s *Store) Open(enableSingle bool) error { return fmt.Errorf("new raft: %s", err) } s.raft = ra + + // Setup the SQLite database. + _ = db.Open(filepath.Join(s.raftDir, "db.sqlite")) return nil } From 4537ed2841a73e7c9f409bf3312717f87ed952f2 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Sun, 14 Feb 2016 11:53:33 -0800 Subject: [PATCH 03/12] Initialize SQLite store --- db/db.go | 8 ++++---- db/db_test.go | 2 +- store/store.go | 10 ++++++++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/db/db.go b/db/db.go index 1aadaa93..e7077e50 100644 --- a/db/db.go +++ b/db/db.go @@ -19,20 +19,20 @@ type RowResult map[string]string type RowResults []map[string]string // New creates a new database. Deletes any existing database. -func New(dbPath string) *DB { +func New(dbPath string) (*DB, error) { _ = os.Remove(dbPath) return Open(dbPath) } // Open an existing database, creating it if it does not exist. -func Open(dbPath string) *DB { +func Open(dbPath string) (*DB, error) { dbc, err := sql.Open("sqlite3", dbPath) if err != nil { - return nil + return nil, err } return &DB{ dbConn: dbc, - } + }, nil } // Close closes the underlying database connection. diff --git a/db/db_test.go b/db/db_test.go index 759f6a97..4f4df733 100644 --- a/db/db_test.go +++ b/db/db_test.go @@ -26,7 +26,7 @@ func (s *DbSuite) Test_DbFileCreation(c *C) { dir, err := ioutil.TempDir("", "rqlite-test-") defer os.RemoveAll(dir) - db := New(path.Join(dir, "test_db")) + db, err := New(path.Join(dir, "test_db")) c.Assert(db, NotNil) err = db.Close() c.Assert(err, IsNil) diff --git a/store/store.go b/store/store.go index f074e4ef..1fc2ea83 100644 --- a/store/store.go +++ b/store/store.go @@ -18,7 +18,7 @@ import ( "github.com/hashicorp/raft" "github.com/hashicorp/raft-boltdb" - "github.com/otoolep/rqlite/db" + sql "github.com/otoolep/rqlite/db" ) const ( @@ -34,6 +34,7 @@ type Store struct { mu sync.Mutex raft *raft.Raft // The consensus mechanism + db *sql.DB // The underlying SQLite store logger *log.Logger } @@ -100,7 +101,12 @@ func (s *Store) Open(enableSingle bool) error { s.raft = ra // Setup the SQLite database. - _ = db.Open(filepath.Join(s.raftDir, "db.sqlite")) + db, err := sql.Open(filepath.Join(s.raftDir, "db.sqlite")) + if err != nil { + return err + } + s.db = db + return nil } From b065655d6ac9c8ccdb5412b01977e7a3e8450e05 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Sun, 14 Feb 2016 11:56:20 -0800 Subject: [PATCH 04/12] Start removal of nonstandard test framework --- db/db_test.go | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/db/db_test.go b/db/db_test.go index 4f4df733..6057d955 100644 --- a/db/db_test.go +++ b/db/db_test.go @@ -5,24 +5,13 @@ import ( "os" "path" "testing" - - . "gopkg.in/check.v1" ) -// Hook up gocheck into the "go test" runner. -func Test(t *testing.T) { - TestingT(t) -} - -type DbSuite struct{} - -var _ = Suite(&DbSuite{}) - /* * Lowest-layer database tests */ -func (s *DbSuite) Test_DbFileCreation(c *C) { +func Test_DbFileCreation(t *testing.T) { dir, err := ioutil.TempDir("", "rqlite-test-") defer os.RemoveAll(dir) @@ -32,7 +21,7 @@ func (s *DbSuite) Test_DbFileCreation(c *C) { c.Assert(err, IsNil) } -func (s *DbSuite) Test_TableCreation(c *C) { +func Test_TableCreation(t *testing.T) { dir, err := ioutil.TempDir("", "rqlite-test-") defer os.RemoveAll(dir) db := New(path.Join(dir, "test_db")) @@ -46,7 +35,7 @@ func (s *DbSuite) Test_TableCreation(c *C) { c.Assert(len(r), Equals, 0) } -func (s *DbSuite) Test_SimpleStatements(c *C) { +func Test_SimpleStatements(t *testing.T) { dir, err := ioutil.TempDir("", "rqlite-test-") defer os.RemoveAll(dir) db := New(path.Join(dir, "test_db")) @@ -96,7 +85,7 @@ func (s *DbSuite) Test_SimpleStatements(c *C) { } } -func (s *DbSuite) Test_FailingSimpleStatements(c *C) { +func Test_FailingSimpleStatements(t *testing.T) { dir, err := ioutil.TempDir("", "rqlite-test-") defer os.RemoveAll(dir) db := New(path.Join(dir, "test_db")) @@ -127,7 +116,7 @@ func (s *DbSuite) Test_FailingSimpleStatements(c *C) { c.Assert(err.Error(), Equals, "near \"utter\": syntax error") } -func (s *DbSuite) Test_SimpleTransactions(c *C) { +func Test_SimpleTransactions(t *testing.T) { dir, err := ioutil.TempDir("", "rqlite-test-") defer os.RemoveAll(dir) db := New(path.Join(dir, "test_db")) @@ -165,7 +154,7 @@ func (s *DbSuite) Test_SimpleTransactions(c *C) { } } -func (s *DbSuite) Test_TransactionsConstraintViolation(c *C) { +func Test_TransactionsConstraintViolation(t *testing.T) { dir, err := ioutil.TempDir("", "rqlite-test-") defer os.RemoveAll(dir) db := New(path.Join(dir, "test_db")) @@ -187,7 +176,7 @@ func (s *DbSuite) Test_TransactionsConstraintViolation(c *C) { c.Assert(len(r), Equals, 0) } -func (s *DbSuite) Test_TransactionsHardFail(c *C) { +func Test_TransactionsHardFail(t *testing.T) { dir, err := ioutil.TempDir("", "rqlite-test-") defer os.RemoveAll(dir) db := New(path.Join(dir, "test_db")) From a67d94f926b4402cccac7d91e72077081c863d73 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Sun, 14 Feb 2016 12:10:35 -0800 Subject: [PATCH 05/12] Continue moving db tests to standard framework --- db/db_test.go | 362 +++++++++++++++++++++++++++----------------------- 1 file changed, 195 insertions(+), 167 deletions(-) diff --git a/db/db_test.go b/db/db_test.go index 6057d955..3d35050b 100644 --- a/db/db_test.go +++ b/db/db_test.go @@ -15,187 +15,215 @@ func Test_DbFileCreation(t *testing.T) { dir, err := ioutil.TempDir("", "rqlite-test-") defer os.RemoveAll(dir) - db, err := New(path.Join(dir, "test_db")) - c.Assert(db, NotNil) + db, err := Open(path.Join(dir, "test_db")) + if err != nil { + t.Fatalf("failed to open new database: %s", err.Error()) + } + if db == nil { + t.Fatal("database is nil") + } err = db.Close() - c.Assert(err, IsNil) + if err != nil { + t.Fatalf("failed to close database: %s", err.Error()) + } } func Test_TableCreation(t *testing.T) { - dir, err := ioutil.TempDir("", "rqlite-test-") - defer os.RemoveAll(dir) - db := New(path.Join(dir, "test_db")) + db, path := mustOpenDatabase() defer db.Close() + defer os.Remove(path) - err = db.Execute("create table foo (id integer not null primary key, name text)") - c.Assert(err, IsNil) - - r, err := db.Query("SELECT * FROM foo") - c.Assert(err, IsNil) - c.Assert(len(r), Equals, 0) -} - -func Test_SimpleStatements(t *testing.T) { - dir, err := ioutil.TempDir("", "rqlite-test-") - defer os.RemoveAll(dir) - db := New(path.Join(dir, "test_db")) - defer db.Close() - - err = db.Execute("create table foo (id integer not null primary key, name text)") - c.Assert(err, IsNil) + err := db.Execute("create table foo (id integer not null primary key, name text)") + if err != nil { + t.Fatalf("failed to create table: %s", err.Error()) + } - err = db.Execute("INSERT INTO foo(name) VALUES(\"fiona\")") - c.Assert(err, IsNil) r, err := db.Query("SELECT * FROM foo") - c.Assert(len(r), Equals, 1) - c.Assert(r[0]["id"], Equals, "1") - c.Assert(r[0]["name"], Equals, "fiona") - - err = db.Execute("INSERT INTO foo(name) VALUES(\"dana\")") - c.Assert(err, IsNil) - r, err = db.Query("SELECT * FROM foo") - c.Assert(len(r), Equals, 2) - c.Assert(r[1]["id"], Equals, "2") - c.Assert(r[1]["name"], Equals, "dana") - - err = db.Execute("UPDATE foo SET Name='Who knows?' WHERE Id=1") - c.Assert(err, IsNil) - r, err = db.Query("SELECT * FROM foo") - c.Assert(len(r), Equals, 2) - c.Assert(r[0]["id"], Equals, "1") - c.Assert(r[0]["name"], Equals, "Who knows?") - - err = db.Execute("DELETE FROM foo WHERE Id=2") - c.Assert(err, IsNil) - r, err = db.Query("SELECT * FROM foo") - c.Assert(len(r), Equals, 1) - c.Assert(r[0]["id"], Equals, "1") - c.Assert(r[0]["name"], Equals, "Who knows?") - - err = db.Execute("DELETE FROM foo WHERE Id=1") - c.Assert(err, IsNil) - - for i := 0; i < 10; i++ { - _ = db.Execute("INSERT INTO foo(name) VALUES(\"philip\")") + if err != nil { + t.Fatalf("failed to query empty table: %s", err.Error()) } - r, err = db.Query("SELECT name FROM foo") - c.Assert(len(r), Equals, 10) - for i := range r { - c.Assert(r[i]["name"], Equals, "philip") + if len(r) != 0 { + t.Fatalf("expected 0 results, got %d", len(r)) } } -func Test_FailingSimpleStatements(t *testing.T) { - dir, err := ioutil.TempDir("", "rqlite-test-") - defer os.RemoveAll(dir) - db := New(path.Join(dir, "test_db")) - defer db.Close() - - err = db.Execute("INSERT INTO foo(name) VALUES(\"fiona\")") - c.Assert(err, NotNil) - c.Assert(err.Error(), Equals, "no such table: foo") - - err = db.Execute("create table foo (id integer not null primary key, name text)") - c.Assert(err, IsNil) - err = db.Execute("create table foo (id integer not null primary key, name text)") - c.Assert(err, NotNil) - c.Assert(err.Error(), Equals, "table foo already exists") - - err = db.Execute("INSERT INTO foo(id, name) VALUES(11, \"fiona\")") - c.Assert(err, IsNil) - err = db.Execute("INSERT INTO foo(id, name) VALUES(11, \"fiona\")") - c.Assert(err, NotNil) - c.Assert(err.Error(), Equals, "UNIQUE constraint failed: foo.id") - - err = db.Execute("SELECT * FROM bar") - c.Assert(err, NotNil) - c.Assert(err.Error(), Equals, "no such table: bar") - - err = db.Execute("utter nonsense") - c.Assert(err, NotNil) - c.Assert(err.Error(), Equals, "near \"utter\": syntax error") -} - -func Test_SimpleTransactions(t *testing.T) { - dir, err := ioutil.TempDir("", "rqlite-test-") - defer os.RemoveAll(dir) - db := New(path.Join(dir, "test_db")) - defer db.Close() - - err = db.Execute("create table foo (id integer not null primary key, name text)") - c.Assert(err, IsNil) - - err = db.StartTransaction() - c.Assert(err, IsNil) - for i := 0; i < 10; i++ { - _ = db.Execute("INSERT INTO foo(name) VALUES(\"philip\")") - } - err = db.CommitTransaction() - c.Assert(err, IsNil) - - r, err := db.Query("SELECT name FROM foo") - c.Assert(len(r), Equals, 10) - for i := range r { - c.Assert(r[i]["name"], Equals, "philip") - } - - err = db.StartTransaction() - c.Assert(err, IsNil) - for i := 0; i < 10; i++ { - _ = db.Execute("INSERT INTO foo(name) VALUES(\"philip\")") +// func Test_SimpleStatements(t *testing.T) { +// dir, err := ioutil.TempDir("", "rqlite-test-") +// defer os.RemoveAll(dir) +// db := New(path.Join(dir, "test_db")) +// defer db.Close() + +// err = db.Execute("create table foo (id integer not null primary key, name text)") +// c.Assert(err, IsNil) + +// err = db.Execute("INSERT INTO foo(name) VALUES(\"fiona\")") +// c.Assert(err, IsNil) +// r, err := db.Query("SELECT * FROM foo") +// c.Assert(len(r), Equals, 1) +// c.Assert(r[0]["id"], Equals, "1") +// c.Assert(r[0]["name"], Equals, "fiona") + +// err = db.Execute("INSERT INTO foo(name) VALUES(\"dana\")") +// c.Assert(err, IsNil) +// r, err = db.Query("SELECT * FROM foo") +// c.Assert(len(r), Equals, 2) +// c.Assert(r[1]["id"], Equals, "2") +// c.Assert(r[1]["name"], Equals, "dana") + +// err = db.Execute("UPDATE foo SET Name='Who knows?' WHERE Id=1") +// c.Assert(err, IsNil) +// r, err = db.Query("SELECT * FROM foo") +// c.Assert(len(r), Equals, 2) +// c.Assert(r[0]["id"], Equals, "1") +// c.Assert(r[0]["name"], Equals, "Who knows?") + +// err = db.Execute("DELETE FROM foo WHERE Id=2") +// c.Assert(err, IsNil) +// r, err = db.Query("SELECT * FROM foo") +// c.Assert(len(r), Equals, 1) +// c.Assert(r[0]["id"], Equals, "1") +// c.Assert(r[0]["name"], Equals, "Who knows?") + +// err = db.Execute("DELETE FROM foo WHERE Id=1") +// c.Assert(err, IsNil) + +// for i := 0; i < 10; i++ { +// _ = db.Execute("INSERT INTO foo(name) VALUES(\"philip\")") +// } +// r, err = db.Query("SELECT name FROM foo") +// c.Assert(len(r), Equals, 10) +// for i := range r { +// c.Assert(r[i]["name"], Equals, "philip") +// } +// } + +// func Test_FailingSimpleStatements(t *testing.T) { +// dir, err := ioutil.TempDir("", "rqlite-test-") +// defer os.RemoveAll(dir) +// db := New(path.Join(dir, "test_db")) +// defer db.Close() + +// err = db.Execute("INSERT INTO foo(name) VALUES(\"fiona\")") +// c.Assert(err, NotNil) +// c.Assert(err.Error(), Equals, "no such table: foo") + +// err = db.Execute("create table foo (id integer not null primary key, name text)") +// c.Assert(err, IsNil) +// err = db.Execute("create table foo (id integer not null primary key, name text)") +// c.Assert(err, NotNil) +// c.Assert(err.Error(), Equals, "table foo already exists") + +// err = db.Execute("INSERT INTO foo(id, name) VALUES(11, \"fiona\")") +// c.Assert(err, IsNil) +// err = db.Execute("INSERT INTO foo(id, name) VALUES(11, \"fiona\")") +// c.Assert(err, NotNil) +// c.Assert(err.Error(), Equals, "UNIQUE constraint failed: foo.id") + +// err = db.Execute("SELECT * FROM bar") +// c.Assert(err, NotNil) +// c.Assert(err.Error(), Equals, "no such table: bar") + +// err = db.Execute("utter nonsense") +// c.Assert(err, NotNil) +// c.Assert(err.Error(), Equals, "near \"utter\": syntax error") +// } + +// func Test_SimpleTransactions(t *testing.T) { +// dir, err := ioutil.TempDir("", "rqlite-test-") +// defer os.RemoveAll(dir) +// db := New(path.Join(dir, "test_db")) +// defer db.Close() + +// err = db.Execute("create table foo (id integer not null primary key, name text)") +// c.Assert(err, IsNil) + +// err = db.StartTransaction() +// c.Assert(err, IsNil) +// for i := 0; i < 10; i++ { +// _ = db.Execute("INSERT INTO foo(name) VALUES(\"philip\")") +// } +// err = db.CommitTransaction() +// c.Assert(err, IsNil) + +// r, err := db.Query("SELECT name FROM foo") +// c.Assert(len(r), Equals, 10) +// for i := range r { +// c.Assert(r[i]["name"], Equals, "philip") +// } + +// err = db.StartTransaction() +// c.Assert(err, IsNil) +// for i := 0; i < 10; i++ { +// _ = db.Execute("INSERT INTO foo(name) VALUES(\"philip\")") +// } +// err = db.RollbackTransaction() +// c.Assert(err, IsNil) + +// r, err = db.Query("select name from foo") // Test lowercase +// c.Assert(len(r), Equals, 10) +// for i := range r { +// c.Assert(r[i]["name"], Equals, "philip") +// } +// } + +// func Test_TransactionsConstraintViolation(t *testing.T) { +// dir, err := ioutil.TempDir("", "rqlite-test-") +// defer os.RemoveAll(dir) +// db := New(path.Join(dir, "test_db")) +// defer db.Close() + +// err = db.Execute("create table foo (id integer not null primary key, name text)") +// c.Assert(err, IsNil) + +// err = db.StartTransaction() +// c.Assert(err, IsNil) +// err = db.Execute("INSERT INTO foo(id, name) VALUES(1, \"fiona\")") +// c.Assert(err, IsNil) +// err = db.Execute("INSERT INTO foo(id, name) VALUES(1, \"fiona\")") +// c.Assert(err, NotNil) +// err = db.RollbackTransaction() +// c.Assert(err, IsNil) + +// r, err := db.Query("SELECT * FROM foo") +// c.Assert(len(r), Equals, 0) +// } + +// func Test_TransactionsHardFail(t *testing.T) { +// dir, err := ioutil.TempDir("", "rqlite-test-") +// defer os.RemoveAll(dir) +// db := New(path.Join(dir, "test_db")) + +// err = db.Execute("create table foo (id integer not null primary key, name text)") +// c.Assert(err, IsNil) +// err = db.Execute("INSERT INTO foo(id, name) VALUES(1, \"fiona\")") +// c.Assert(err, IsNil) + +// err = db.StartTransaction() +// c.Assert(err, IsNil) +// err = db.Execute("INSERT INTO foo(id, name) VALUES(2, \"dana\")") +// c.Assert(err, IsNil) +// db.Close() + +// db = Open(path.Join(dir, "test_db")) +// c.Assert(db, NotNil) +// r, err := db.Query("SELECT * FROM foo") +// c.Assert(len(r), Equals, 1) +// c.Assert(r[0]["name"], Equals, "fiona") +// db.Close() +// } + +func mustOpenDatabase() (*DB, string) { + var err error + f, err := ioutil.TempFile("", "rqlilte-test-") + if err != nil { + panic("failed to create temp file") } - err = db.RollbackTransaction() - c.Assert(err, IsNil) + f.Close() - r, err = db.Query("select name from foo") // Test lowercase - c.Assert(len(r), Equals, 10) - for i := range r { - c.Assert(r[i]["name"], Equals, "philip") + db, err := Open(f.Name()) + if err != nil { + panic("failed to open database") } -} - -func Test_TransactionsConstraintViolation(t *testing.T) { - dir, err := ioutil.TempDir("", "rqlite-test-") - defer os.RemoveAll(dir) - db := New(path.Join(dir, "test_db")) - defer db.Close() - err = db.Execute("create table foo (id integer not null primary key, name text)") - c.Assert(err, IsNil) - - err = db.StartTransaction() - c.Assert(err, IsNil) - err = db.Execute("INSERT INTO foo(id, name) VALUES(1, \"fiona\")") - c.Assert(err, IsNil) - err = db.Execute("INSERT INTO foo(id, name) VALUES(1, \"fiona\")") - c.Assert(err, NotNil) - err = db.RollbackTransaction() - c.Assert(err, IsNil) - - r, err := db.Query("SELECT * FROM foo") - c.Assert(len(r), Equals, 0) -} - -func Test_TransactionsHardFail(t *testing.T) { - dir, err := ioutil.TempDir("", "rqlite-test-") - defer os.RemoveAll(dir) - db := New(path.Join(dir, "test_db")) - - err = db.Execute("create table foo (id integer not null primary key, name text)") - c.Assert(err, IsNil) - err = db.Execute("INSERT INTO foo(id, name) VALUES(1, \"fiona\")") - c.Assert(err, IsNil) - - err = db.StartTransaction() - c.Assert(err, IsNil) - err = db.Execute("INSERT INTO foo(id, name) VALUES(2, \"dana\")") - c.Assert(err, IsNil) - db.Close() - - db = Open(path.Join(dir, "test_db")) - c.Assert(db, NotNil) - r, err := db.Query("SELECT * FROM foo") - c.Assert(len(r), Equals, 1) - c.Assert(r[0]["name"], Equals, "fiona") - db.Close() + return db, f.Name() } From 5a40c0f3e95b1ca52afbef927e2d648a8c704894 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Sun, 14 Feb 2016 16:15:31 -0800 Subject: [PATCH 06/12] Unit-test simple database insertion cases --- db/db_test.go | 101 ++++++++++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 44 deletions(-) diff --git a/db/db_test.go b/db/db_test.go index 3d35050b..fd0c145b 100644 --- a/db/db_test.go +++ b/db/db_test.go @@ -1,6 +1,7 @@ package db import ( + "encoding/json" "io/ioutil" "os" "path" @@ -47,55 +48,59 @@ func Test_TableCreation(t *testing.T) { } } -// func Test_SimpleStatements(t *testing.T) { -// dir, err := ioutil.TempDir("", "rqlite-test-") -// defer os.RemoveAll(dir) -// db := New(path.Join(dir, "test_db")) -// defer db.Close() - -// err = db.Execute("create table foo (id integer not null primary key, name text)") -// c.Assert(err, IsNil) - -// err = db.Execute("INSERT INTO foo(name) VALUES(\"fiona\")") -// c.Assert(err, IsNil) -// r, err := db.Query("SELECT * FROM foo") -// c.Assert(len(r), Equals, 1) -// c.Assert(r[0]["id"], Equals, "1") -// c.Assert(r[0]["name"], Equals, "fiona") +func Test_SimpleStatements(t *testing.T) { + db, path := mustOpenDatabase() + defer db.Close() + defer os.Remove(path) -// err = db.Execute("INSERT INTO foo(name) VALUES(\"dana\")") -// c.Assert(err, IsNil) -// r, err = db.Query("SELECT * FROM foo") -// c.Assert(len(r), Equals, 2) -// c.Assert(r[1]["id"], Equals, "2") -// c.Assert(r[1]["name"], Equals, "dana") + if err := db.Execute("create table foo (id integer not null primary key, name text)"); err != nil { + t.Fatalf("failed to create table: %s", err.Error()) + } -// err = db.Execute("UPDATE foo SET Name='Who knows?' WHERE Id=1") -// c.Assert(err, IsNil) -// r, err = db.Query("SELECT * FROM foo") -// c.Assert(len(r), Equals, 2) -// c.Assert(r[0]["id"], Equals, "1") -// c.Assert(r[0]["name"], Equals, "Who knows?") + if err := db.Execute(`INSERT INTO foo(name) VALUES("fiona")`); err != nil { + t.Fatalf("failed to insert record: %s", err.Error()) + } + r, err := db.Query("SELECT * FROM foo") + if err != nil { + t.Fatalf("failed to query empty table: %s", err.Error()) + } + if exp, got := `[{"id":"1","name":"fiona"}]`, asJson(r); exp != got { + t.Fatalf("unexpected results for query, expected %s, got %s", exp, got) + } -// err = db.Execute("DELETE FROM foo WHERE Id=2") -// c.Assert(err, IsNil) -// r, err = db.Query("SELECT * FROM foo") -// c.Assert(len(r), Equals, 1) -// c.Assert(r[0]["id"], Equals, "1") -// c.Assert(r[0]["name"], Equals, "Who knows?") + if err := db.Execute(`INSERT INTO foo(name) VALUES("aoife")`); err != nil { + t.Fatalf("failed to insert record: %s", err.Error()) + } + r, err = db.Query("SELECT * FROM foo") + if err != nil { + t.Fatalf("failed to query table: %s", err.Error()) + } + if exp, got := `[{"id":"1","name":"fiona"},{"id":"2","name":"aoife"}]`, asJson(r); exp != got { + t.Fatalf("unexpected results for query, expected %s, got %s", exp, got) + } -// err = db.Execute("DELETE FROM foo WHERE Id=1") -// c.Assert(err, IsNil) + if err := db.Execute("UPDATE foo SET Name='Who knows?' WHERE Id=1"); err != nil { + t.Fatalf("failed to update record: %s", err.Error()) + } + r, err = db.Query("SELECT * FROM foo") + if err != nil { + t.Fatalf("failed to query table: %s", err.Error()) + } + if exp, got := `[{"id":"1","name":"Who knows?"},{"id":"2","name":"aoife"}]`, asJson(r); exp != got { + t.Fatalf("unexpected results for query, expected %s, got %s", exp, got) + } -// for i := 0; i < 10; i++ { -// _ = db.Execute("INSERT INTO foo(name) VALUES(\"philip\")") -// } -// r, err = db.Query("SELECT name FROM foo") -// c.Assert(len(r), Equals, 10) -// for i := range r { -// c.Assert(r[i]["name"], Equals, "philip") -// } -// } + if err := db.Execute("DELETE FROM foo WHERE Id=2"); err != nil { + t.Fatalf("failed to delete record: %s", err.Error()) + } + r, err = db.Query("SELECT * FROM foo") + if err != nil { + t.Fatalf("failed to query table: %s", err.Error()) + } + if exp, got := `[{"id":"1","name":"Who knows?"}]`, asJson(r); exp != got { + t.Fatalf("unexpected results for query, expected %s, got %s", exp, got) + } +} // func Test_FailingSimpleStatements(t *testing.T) { // dir, err := ioutil.TempDir("", "rqlite-test-") @@ -227,3 +232,11 @@ func mustOpenDatabase() (*DB, string) { return db, f.Name() } + +func asJson(v interface{}) string { + b, err := json.Marshal(v) + if err != nil { + panic("failed to JSON marshal value") + } + return string(b) +} From ab8c83fd25367b98516cdfd2ba48a2d53648da7e Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Sun, 14 Feb 2016 21:25:54 -0800 Subject: [PATCH 07/12] Test_FailingSimpleStatements now passing --- db/db_test.go | 67 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/db/db_test.go b/db/db_test.go index fd0c145b..13bafcae 100644 --- a/db/db_test.go +++ b/db/db_test.go @@ -102,36 +102,53 @@ func Test_SimpleStatements(t *testing.T) { } } -// func Test_FailingSimpleStatements(t *testing.T) { -// dir, err := ioutil.TempDir("", "rqlite-test-") -// defer os.RemoveAll(dir) -// db := New(path.Join(dir, "test_db")) -// defer db.Close() +func Test_FailingSimpleStatements(t *testing.T) { + db, path := mustOpenDatabase() + defer db.Close() + defer os.Remove(path) -// err = db.Execute("INSERT INTO foo(name) VALUES(\"fiona\")") -// c.Assert(err, NotNil) -// c.Assert(err.Error(), Equals, "no such table: foo") + err := db.Execute(`INSERT INTO foo(name) VALUES("fiona")`) + if err == nil { + t.Fatal("inserted record into empty table OK") + } + if err.Error() != "no such table: foo" { + t.Fatal("unexpected error returned") + } -// err = db.Execute("create table foo (id integer not null primary key, name text)") -// c.Assert(err, IsNil) -// err = db.Execute("create table foo (id integer not null primary key, name text)") -// c.Assert(err, NotNil) -// c.Assert(err.Error(), Equals, "table foo already exists") + if err = db.Execute("create table foo (id integer not null primary key, name text)"); err != nil { + t.Fatalf("failed to create table: %s", err.Error()) + } + if err = db.Execute("create table foo (id integer not null primary key, name text)"); err == nil { + t.Fatal("duplicate table created OK") + } + if err.Error() != "table foo already exists" { + t.Fatalf("unexpected error returned: %s", err.Error()) + } -// err = db.Execute("INSERT INTO foo(id, name) VALUES(11, \"fiona\")") -// c.Assert(err, IsNil) -// err = db.Execute("INSERT INTO foo(id, name) VALUES(11, \"fiona\")") -// c.Assert(err, NotNil) -// c.Assert(err.Error(), Equals, "UNIQUE constraint failed: foo.id") + if err = db.Execute(`INSERT INTO foo(id, name) VALUES(11, "fiona")`); err != nil { + t.Fatalf("failed to insert record: %s", err.Error()) + } + if err = db.Execute(`INSERT INTO foo(id, name) VALUES(11, "fiona")`); err == nil { + t.Fatal("duplicated record inserted OK") + } + if err.Error() != "UNIQUE constraint failed: foo.id" { + t.Fatal("unexpected error returned") + } -// err = db.Execute("SELECT * FROM bar") -// c.Assert(err, NotNil) -// c.Assert(err.Error(), Equals, "no such table: bar") + if err = db.Execute("SELECT * FROM bar"); err == nil { + t.Fatal("no error when querying non-existant table") + } + if err.Error() != "no such table: bar" { + t.Fatal("unexpected error returned") + } -// err = db.Execute("utter nonsense") -// c.Assert(err, NotNil) -// c.Assert(err.Error(), Equals, "near \"utter\": syntax error") -// } + if err = db.Execute("utter nonsense"); err == nil { + t.Fatal("no error when issuing nonsense query") + } + if err.Error() != `near "utter": syntax error` { + t.Fatal("unexpected error returned") + } +} // func Test_SimpleTransactions(t *testing.T) { // dir, err := ioutil.TempDir("", "rqlite-test-") From 4ba36f780a5282cc6f12943e4615c8b82b988b89 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Sun, 14 Feb 2016 21:31:01 -0800 Subject: [PATCH 08/12] Test_SimpleTransactions now passing --- db/db_test.go | 72 ++++++++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/db/db_test.go b/db/db_test.go index 13bafcae..dd374897 100644 --- a/db/db_test.go +++ b/db/db_test.go @@ -150,43 +150,51 @@ func Test_FailingSimpleStatements(t *testing.T) { } } -// func Test_SimpleTransactions(t *testing.T) { -// dir, err := ioutil.TempDir("", "rqlite-test-") -// defer os.RemoveAll(dir) -// db := New(path.Join(dir, "test_db")) -// defer db.Close() +func Test_SimpleTransactions(t *testing.T) { + db, path := mustOpenDatabase() + defer db.Close() + defer os.Remove(path) -// err = db.Execute("create table foo (id integer not null primary key, name text)") -// c.Assert(err, IsNil) + if err := db.Execute("create table foo (id integer not null primary key, name text)"); err != nil { + t.Fatalf("failed to create table: %s", err.Error()) + } -// err = db.StartTransaction() -// c.Assert(err, IsNil) -// for i := 0; i < 10; i++ { -// _ = db.Execute("INSERT INTO foo(name) VALUES(\"philip\")") -// } -// err = db.CommitTransaction() -// c.Assert(err, IsNil) + if err := db.StartTransaction(); err != nil { + t.Fatalf("failed to start transaction: %s", err.Error()) + } + for i := 0; i < 10; i++ { + _ = db.Execute(`INSERT INTO foo(name) VALUES("philip")`) + } + if err := db.CommitTransaction(); err != nil { + t.Fatalf("failed to commit transaction: %s", err.Error()) + } -// r, err := db.Query("SELECT name FROM foo") -// c.Assert(len(r), Equals, 10) -// for i := range r { -// c.Assert(r[i]["name"], Equals, "philip") -// } + r, err := db.Query("SELECT name FROM foo") + if err != nil { + t.Fatalf("failed to query after commited transaction: %s", err.Error()) + } + if len(r) != 10 { + t.Fatalf("incorrect number of results returned: %d", len(r)) + } -// err = db.StartTransaction() -// c.Assert(err, IsNil) -// for i := 0; i < 10; i++ { -// _ = db.Execute("INSERT INTO foo(name) VALUES(\"philip\")") -// } -// err = db.RollbackTransaction() -// c.Assert(err, IsNil) + if err := db.StartTransaction(); err != nil { + t.Fatalf("failed to start transaction: %s", err.Error()) + } + for i := 0; i < 10; i++ { + _ = db.Execute(`INSERT INTO foo(name) VALUES("philip")`) + } + if err := db.RollbackTransaction(); err != nil { + t.Fatalf("failed to rollback transaction: %s", err.Error()) + } -// r, err = db.Query("select name from foo") // Test lowercase -// c.Assert(len(r), Equals, 10) -// for i := range r { -// c.Assert(r[i]["name"], Equals, "philip") -// } -// } + r, err = db.Query("SELECT name FROM foo") + if err != nil { + t.Fatalf("failed to query after commited transaction: %s", err.Error()) + } + if len(r) != 10 { + t.Fatalf("incorrect number of results returned: %d", len(r)) + } +} // func Test_TransactionsConstraintViolation(t *testing.T) { // dir, err := ioutil.TempDir("", "rqlite-test-") From 777543ea6559a5c857c1b9a7cd531c22fb830651 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Sun, 14 Feb 2016 21:38:59 -0800 Subject: [PATCH 09/12] Test_TransactionsConstraintViolation now passing --- db/db_test.go | 50 +++++++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/db/db_test.go b/db/db_test.go index dd374897..ee6ad14e 100644 --- a/db/db_test.go +++ b/db/db_test.go @@ -196,27 +196,39 @@ func Test_SimpleTransactions(t *testing.T) { } } -// func Test_TransactionsConstraintViolation(t *testing.T) { -// dir, err := ioutil.TempDir("", "rqlite-test-") -// defer os.RemoveAll(dir) -// db := New(path.Join(dir, "test_db")) -// defer db.Close() - -// err = db.Execute("create table foo (id integer not null primary key, name text)") -// c.Assert(err, IsNil) +func Test_TransactionsConstraintViolation(t *testing.T) { + db, path := mustOpenDatabase() + defer db.Close() + defer os.Remove(path) + var err error -// err = db.StartTransaction() -// c.Assert(err, IsNil) -// err = db.Execute("INSERT INTO foo(id, name) VALUES(1, \"fiona\")") -// c.Assert(err, IsNil) -// err = db.Execute("INSERT INTO foo(id, name) VALUES(1, \"fiona\")") -// c.Assert(err, NotNil) -// err = db.RollbackTransaction() -// c.Assert(err, IsNil) + if err = db.Execute("create table foo (id integer not null primary key, name text)"); err != nil { + t.Fatalf("failed to create table: %s", err.Error()) + } -// r, err := db.Query("SELECT * FROM foo") -// c.Assert(len(r), Equals, 0) -// } + if err = db.StartTransaction(); err != nil { + t.Fatalf("failed to start transaction: %s", err.Error()) + } + if err = db.Execute(`INSERT INTO foo(id, name) VALUES(11, "fiona")`); err != nil { + t.Fatalf("failed to insert record: %s", err.Error()) + } + if err = db.Execute(`INSERT INTO foo(id, name) VALUES(11, "fiona")`); err == nil { + t.Fatal("duplicated record inserted OK") + } + if err.Error() != "UNIQUE constraint failed: foo.id" { + t.Fatal("unexpected error returned") + } + if err := db.RollbackTransaction(); err != nil { + t.Fatalf("failed to rollback transaction: %s", err.Error()) + } + r, err := db.Query("SELECT name FROM foo") + if err != nil { + t.Fatalf("failed to query after commited transaction: %s", err.Error()) + } + if len(r) != 0 { + t.Fatalf("incorrect number of results returned: %d", len(r)) + } +} // func Test_TransactionsHardFail(t *testing.T) { // dir, err := ioutil.TempDir("", "rqlite-test-") From 545632402021dc8a1ea6ec2f7b81ce29d0ddb9d7 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Sun, 14 Feb 2016 21:44:47 -0800 Subject: [PATCH 10/12] Test_TransactionsHardFail now passes --- db/db_test.go | 58 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/db/db_test.go b/db/db_test.go index ee6ad14e..dd372b32 100644 --- a/db/db_test.go +++ b/db/db_test.go @@ -230,29 +230,41 @@ func Test_TransactionsConstraintViolation(t *testing.T) { } } -// func Test_TransactionsHardFail(t *testing.T) { -// dir, err := ioutil.TempDir("", "rqlite-test-") -// defer os.RemoveAll(dir) -// db := New(path.Join(dir, "test_db")) - -// err = db.Execute("create table foo (id integer not null primary key, name text)") -// c.Assert(err, IsNil) -// err = db.Execute("INSERT INTO foo(id, name) VALUES(1, \"fiona\")") -// c.Assert(err, IsNil) - -// err = db.StartTransaction() -// c.Assert(err, IsNil) -// err = db.Execute("INSERT INTO foo(id, name) VALUES(2, \"dana\")") -// c.Assert(err, IsNil) -// db.Close() - -// db = Open(path.Join(dir, "test_db")) -// c.Assert(db, NotNil) -// r, err := db.Query("SELECT * FROM foo") -// c.Assert(len(r), Equals, 1) -// c.Assert(r[0]["name"], Equals, "fiona") -// db.Close() -// } +func Test_TransactionsHardFail(t *testing.T) { + db, path := mustOpenDatabase() + defer db.Close() + defer os.Remove(path) + var err error + + if err = db.Execute("create table foo (id integer not null primary key, name text)"); err != nil { + t.Fatalf("failed to create table: %s", err.Error()) + } + if err = db.Execute(`INSERT INTO foo(id, name) VALUES(1, "fiona")`); err != nil { + t.Fatalf("failed to insert record: %s", err.Error()) + } + + if err = db.StartTransaction(); err != nil { + t.Fatalf("failed to start transaction: %s", err.Error()) + } + if err = db.Execute(`INSERT INTO foo(id, name) VALUES(2, "fiona")`); err != nil { + t.Fatalf("failed to insert record: %s", err.Error()) + } + if err = db.Close(); err != nil { + t.Fatalf("failed to close database: %s", err.Error()) + } + + db, err = Open(path) + if err != nil { + t.Fatalf("failed to re-open database: %s", err.Error()) + } + r, err := db.Query("SELECT * FROM foo") + if err != nil { + t.Fatalf("failed to query after hard close: %s", err.Error()) + } + if len(r) != 1 { + t.Fatalf("incorrect number of results returned: %d", len(r)) + } +} func mustOpenDatabase() (*DB, string) { var err error From d8c58f5db153035c8fac50b22857b45cf0de7caf Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Sun, 14 Feb 2016 21:45:23 -0800 Subject: [PATCH 11/12] Better name for test helper --- db/db_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/db/db_test.go b/db/db_test.go index dd372b32..1107231e 100644 --- a/db/db_test.go +++ b/db/db_test.go @@ -30,7 +30,7 @@ func Test_DbFileCreation(t *testing.T) { } func Test_TableCreation(t *testing.T) { - db, path := mustOpenDatabase() + db, path := mustCreateDatabase() defer db.Close() defer os.Remove(path) @@ -49,7 +49,7 @@ func Test_TableCreation(t *testing.T) { } func Test_SimpleStatements(t *testing.T) { - db, path := mustOpenDatabase() + db, path := mustCreateDatabase() defer db.Close() defer os.Remove(path) @@ -103,7 +103,7 @@ func Test_SimpleStatements(t *testing.T) { } func Test_FailingSimpleStatements(t *testing.T) { - db, path := mustOpenDatabase() + db, path := mustCreateDatabase() defer db.Close() defer os.Remove(path) @@ -151,7 +151,7 @@ func Test_FailingSimpleStatements(t *testing.T) { } func Test_SimpleTransactions(t *testing.T) { - db, path := mustOpenDatabase() + db, path := mustCreateDatabase() defer db.Close() defer os.Remove(path) @@ -197,7 +197,7 @@ func Test_SimpleTransactions(t *testing.T) { } func Test_TransactionsConstraintViolation(t *testing.T) { - db, path := mustOpenDatabase() + db, path := mustCreateDatabase() defer db.Close() defer os.Remove(path) var err error @@ -231,7 +231,7 @@ func Test_TransactionsConstraintViolation(t *testing.T) { } func Test_TransactionsHardFail(t *testing.T) { - db, path := mustOpenDatabase() + db, path := mustCreateDatabase() defer db.Close() defer os.Remove(path) var err error @@ -266,7 +266,7 @@ func Test_TransactionsHardFail(t *testing.T) { } } -func mustOpenDatabase() (*DB, string) { +func mustCreateDatabase() (*DB, string) { var err error f, err := ioutil.TempFile("", "rqlilte-test-") if err != nil { From b49963ac32c212124788a84d7ce85fffb8a77632 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Sun, 14 Feb 2016 21:47:37 -0800 Subject: [PATCH 12/12] Rename CLI to more standard version --- cmd/{rqlite-cli => rqlite}/README.md | 0 cmd/{rqlite-cli => rqlite}/main.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename cmd/{rqlite-cli => rqlite}/README.md (100%) rename cmd/{rqlite-cli => rqlite}/main.go (100%) diff --git a/cmd/rqlite-cli/README.md b/cmd/rqlite/README.md similarity index 100% rename from cmd/rqlite-cli/README.md rename to cmd/rqlite/README.md diff --git a/cmd/rqlite-cli/main.go b/cmd/rqlite/main.go similarity index 100% rename from cmd/rqlite-cli/main.go rename to cmd/rqlite/main.go