github.com/ladydascalie/elvish@v0.0.0-20170703214355-2964dd3ece7f/store/store.go (about) 1 // Package store abstracts the persistent storage used by elvish. 2 package store 3 4 import ( 5 "database/sql" 6 "fmt" 7 "net/url" 8 "sync" 9 10 "github.com/elves/elvish/util" 11 12 _ "github.com/mattn/go-sqlite3" // enable the "sqlite3" SQL driver 13 ) 14 15 var logger = util.GetLogger("[store] ") 16 var initDB = map[string](func(*sql.DB) error){} 17 18 // Store is the permanent storage backend for elvish. It is not thread-safe. In 19 // particular, the store may be closed while another goroutine is still 20 // accessing the store. To prevent bad things from happening, every time the 21 // main goroutine spawns a new goroutine to operate on the store, it should call 22 // Waits.Add(1) in the main goroutine before spawning another goroutine, and 23 // call Waits.Done() in the spawned goroutine after the operation is finished. 24 type Store struct { 25 db *sql.DB 26 // Waits is used for registering outstanding operations on the store. 27 waits sync.WaitGroup 28 } 29 30 // DefaultDB returns the default database for storage. 31 func DefaultDB(dbname string) (*sql.DB, error) { 32 uri := "file:" + url.QueryEscape(dbname) + 33 "?mode=rwc&cache=shared&vfs=unix-dotfile" 34 db, err := sql.Open("sqlite3", uri) 35 if err == nil { 36 db.SetMaxOpenConns(1) 37 } 38 return db, err 39 } 40 41 // NewStore creates a new Store with the default database. 42 func NewStore(dbname string) (*Store, error) { 43 db, err := DefaultDB(dbname) 44 if err != nil { 45 return nil, err 46 } 47 return NewStoreDB(db) 48 } 49 50 // NewStoreDB creates a new Store with a custom database. The database must be 51 // a SQLite database. 52 func NewStoreDB(db *sql.DB) (*Store, error) { 53 logger.Println("initializing store") 54 defer logger.Println("initialized store") 55 st := &Store{db, sync.WaitGroup{}} 56 57 if SchemaUpToDate(db) { 58 logger.Println("DB schema up to date") 59 } else { 60 for name, fn := range initDB { 61 err := fn(db) 62 if err != nil { 63 return nil, fmt.Errorf("failed to %s: %v", name, err) 64 } 65 } 66 } 67 68 return st, nil 69 } 70 71 // Waits returns a WaitGroup used to register outstanding storage requests when 72 // making calls asynchronously. 73 func (s *Store) Waits() *sync.WaitGroup { 74 return &s.waits 75 } 76 77 // Close waits for all outstanding operations to finish, and closes the 78 // database. 79 func (s *Store) Close() error { 80 if s == nil || s.db == nil { 81 return nil 82 } 83 s.waits.Wait() 84 return s.db.Close() 85 }