github.com/pjdufour-truss/pop@v4.11.2-0.20190705085848-4c90b0ff4d5a+incompatible/dialect_sqlite.go (about) 1 // +build sqlite 2 3 package pop 4 5 import ( 6 "fmt" 7 "io" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/gobuffalo/fizz" 16 "github.com/gobuffalo/fizz/translators" 17 "github.com/gobuffalo/pop/columns" 18 "github.com/gobuffalo/pop/internal/defaults" 19 "github.com/gobuffalo/pop/logging" 20 _ "github.com/mattn/go-sqlite3" // Load SQLite3 CGo driver 21 "github.com/pkg/errors" 22 ) 23 24 const nameSQLite3 = "sqlite3" 25 26 func init() { 27 AvailableDialects = append(AvailableDialects, nameSQLite3) 28 dialectSynonyms["sqlite"] = nameSQLite3 29 urlParser[nameSQLite3] = urlParserSQLite3 30 newConnection[nameSQLite3] = newSQLite 31 } 32 33 var _ dialect = &sqlite{} 34 35 type sqlite struct { 36 commonDialect 37 gil *sync.Mutex 38 smGil *sync.Mutex 39 } 40 41 func (m *sqlite) Name() string { 42 return nameSQLite3 43 } 44 45 func (m *sqlite) Details() *ConnectionDetails { 46 return m.ConnectionDetails 47 } 48 49 func (m *sqlite) URL() string { 50 return m.ConnectionDetails.Database + "?_busy_timeout=5000" 51 } 52 53 func (m *sqlite) MigrationURL() string { 54 return m.ConnectionDetails.URL 55 } 56 57 func (m *sqlite) Create(s store, model *Model, cols columns.Columns) error { 58 return m.locker(m.smGil, func() error { 59 keyType := model.PrimaryKeyType() 60 switch keyType { 61 case "int", "int64": 62 var id int64 63 w := cols.Writeable() 64 var query string 65 if len(w.Cols) > 0 { 66 query = fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", model.TableName(), w.String(), w.SymbolizedString()) 67 } else { 68 query = fmt.Sprintf("INSERT INTO %s DEFAULT VALUES", model.TableName()) 69 } 70 log(logging.SQL, query) 71 res, err := s.NamedExec(query, model.Value) 72 if err != nil { 73 return err 74 } 75 id, err = res.LastInsertId() 76 if err == nil { 77 model.setID(id) 78 } 79 if err != nil { 80 return err 81 } 82 return nil 83 } 84 return errors.Wrap(genericCreate(s, model, cols), "sqlite create") 85 }) 86 } 87 88 func (m *sqlite) Update(s store, model *Model, cols columns.Columns) error { 89 return m.locker(m.smGil, func() error { 90 return errors.Wrap(genericUpdate(s, model, cols), "sqlite update") 91 }) 92 } 93 94 func (m *sqlite) Destroy(s store, model *Model) error { 95 return m.locker(m.smGil, func() error { 96 return errors.Wrap(genericDestroy(s, model), "sqlite destroy") 97 }) 98 } 99 100 func (m *sqlite) SelectOne(s store, model *Model, query Query) error { 101 return m.locker(m.smGil, func() error { 102 return errors.Wrap(genericSelectOne(s, model, query), "sqlite select one") 103 }) 104 } 105 106 func (m *sqlite) SelectMany(s store, models *Model, query Query) error { 107 return m.locker(m.smGil, func() error { 108 return errors.Wrap(genericSelectMany(s, models, query), "sqlite select many") 109 }) 110 } 111 112 func (m *sqlite) Lock(fn func() error) error { 113 return m.locker(m.gil, fn) 114 } 115 116 func (m *sqlite) locker(l *sync.Mutex, fn func() error) error { 117 if defaults.String(m.Details().Options["lock"], "true") == "true" { 118 defer l.Unlock() 119 l.Lock() 120 } 121 err := fn() 122 attempts := 0 123 for err != nil && err.Error() == "database is locked" && attempts <= m.Details().RetryLimit() { 124 time.Sleep(m.Details().RetrySleep()) 125 err = fn() 126 attempts++ 127 } 128 return err 129 } 130 131 func (m *sqlite) CreateDB() error { 132 d := filepath.Dir(m.ConnectionDetails.Database) 133 err := os.MkdirAll(d, 0766) 134 if err != nil { 135 return errors.Wrapf(err, "could not create SQLite database %s", m.ConnectionDetails.Database) 136 } 137 _, err = os.Create(m.ConnectionDetails.Database) 138 if err != nil { 139 return errors.Wrapf(err, "could not create SQLite database %s", m.ConnectionDetails.Database) 140 } 141 142 log(logging.Info, "created database %s", m.ConnectionDetails.Database) 143 return nil 144 } 145 146 func (m *sqlite) DropDB() error { 147 err := os.Remove(m.ConnectionDetails.Database) 148 if err != nil { 149 return errors.Wrapf(err, "could not drop SQLite database %s", m.ConnectionDetails.Database) 150 } 151 log(logging.Info, "dropped database %s", m.ConnectionDetails.Database) 152 return nil 153 } 154 155 func (m *sqlite) TranslateSQL(sql string) string { 156 return sql 157 } 158 159 func (m *sqlite) FizzTranslator() fizz.Translator { 160 return translators.NewSQLite(m.Details().Database) 161 } 162 163 func (m *sqlite) DumpSchema(w io.Writer) error { 164 cmd := exec.Command("sqlite3", m.Details().Database, ".schema") 165 return genericDumpSchema(m.Details(), cmd, w) 166 } 167 168 func (m *sqlite) LoadSchema(r io.Reader) error { 169 cmd := exec.Command("sqlite3", m.ConnectionDetails.Database) 170 in, err := cmd.StdinPipe() 171 if err != nil { 172 return err 173 } 174 go func() { 175 defer in.Close() 176 io.Copy(in, r) 177 }() 178 log(logging.SQL, strings.Join(cmd.Args, " ")) 179 err = cmd.Start() 180 if err != nil { 181 return err 182 } 183 184 err = cmd.Wait() 185 if err != nil { 186 return err 187 } 188 189 log(logging.Info, "loaded schema for %s", m.Details().Database) 190 return nil 191 } 192 193 func (m *sqlite) TruncateAll(tx *Connection) error { 194 const tableNames = `SELECT name FROM sqlite_master WHERE type = "table"` 195 names := []struct { 196 Name string `db:"name"` 197 }{} 198 199 err := tx.RawQuery(tableNames).All(&names) 200 if err != nil { 201 return err 202 } 203 if len(names) == 0 { 204 return nil 205 } 206 stmts := []string{} 207 for _, n := range names { 208 stmts = append(stmts, fmt.Sprintf("DELETE FROM %s", n.Name)) 209 } 210 return tx.RawQuery(strings.Join(stmts, "; ")).Exec() 211 } 212 213 func newSQLite(deets *ConnectionDetails) (dialect, error) { 214 deets.URL = fmt.Sprintf("sqlite3://%s", deets.Database) 215 cd := &sqlite{ 216 gil: &sync.Mutex{}, 217 smGil: &sync.Mutex{}, 218 commonDialect: commonDialect{ConnectionDetails: deets}, 219 } 220 221 return cd, nil 222 } 223 224 func urlParserSQLite3(cd *ConnectionDetails) error { 225 db := strings.TrimPrefix(cd.URL, "sqlite://") 226 cd.Database = strings.TrimPrefix(db, "sqlite3://") 227 return nil 228 }