github.com/gocaveman/caveman@v0.0.0-20191211162744-0ddf99dbdf6e/regions/regionsdb/regionsdb.go (about) 1 // Database and flat file persistence for regions. 2 package regionsdb 3 4 import ( 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path" 9 "sync" 10 11 "github.com/gocaveman/caveman/autowire" 12 "github.com/gocaveman/caveman/filesystem" 13 "github.com/gocaveman/caveman/migrate" 14 "github.com/gocaveman/caveman/migrate/migrateregistry" 15 "github.com/gocaveman/caveman/regions" 16 "github.com/gocraft/dbr" 17 yaml "gopkg.in/yaml.v2" 18 ) 19 20 // DefaultRegionsMigrations is all of our migrations for this store. 21 var DefaultRegionsMigrations migrate.MigrationList 22 23 func init() { 24 25 // register in migrateregistry and with autowire for all 3 databases 26 reg := func(m *migrate.SQLTmplMigration) { 27 // TODO: see if we can compact these back down to one-liners 28 var rm migrate.Migration 29 rm = m.NewWithDriverName("sqlite3") 30 DefaultRegionsMigrations = append(DefaultRegionsMigrations, rm) 31 autowire.Populate(migrateregistry.MustRegister(rm)) 32 rm = m.NewWithDriverName("mysql") 33 DefaultRegionsMigrations = append(DefaultRegionsMigrations, rm) 34 autowire.Populate(migrateregistry.MustRegister(rm)) 35 rm = m.NewWithDriverName("postgres") 36 DefaultRegionsMigrations = append(DefaultRegionsMigrations, rm) 37 autowire.Populate(migrateregistry.MustRegister(rm)) 38 } 39 40 reg(&migrate.SQLTmplMigration{ 41 // DriverNameValue set by reg 42 CategoryValue: "regions", 43 VersionValue: "0001_region_definition_create", // must be unique and indicates sequence 44 UpSQL: []string{ 45 `CREATE TABLE {{.TablePrefix}}region_definition ( 46 definition_id VARCHAR(255), 47 region_name VARCHAR(255), 48 sequence DOUBLE, 49 disabled INTEGER, 50 template_name VARCHAR(255), 51 cond_include_paths TEXT, 52 cond_exclude_paths TEXT, 53 cond_template TEXT, 54 context_meta TEXT, 55 PRIMARY KEY (definition_id) 56 )`, 57 }, 58 DownSQL: []string{ 59 `DROP TABLE {{.TablePrefix}}region_definition`, 60 }, 61 }) 62 63 } 64 65 // DBStore implements Store against a database table and optionally provides persistence to a flat file. 66 type DBStore struct { 67 DBDriver string `autowire:"db.DriverName"` 68 DBDSN string `autowire:"db.DataSourceName"` 69 TablePrefix string `autowire:"db.TablePrefix,optional"` 70 71 // if FileSystem is non-nil then flat-file persistence is enabled in addition to database functionality 72 FileSystem filesystem.FileSystem `autowire:"db.FlatFileSystem,optional"` 73 FilePathPrefix string `autowire:"db.FlatFilePrefix,optional"` 74 FilePathSuffix string // defaults to "regions.yaml" 75 76 conn *dbr.Connection 77 needsFileLoad bool // set this to trigger a reload before the next operation, used at startup 78 rwmu sync.RWMutex // for file operations 79 } 80 81 func (s *DBStore) AfterWire() error { 82 83 if s.FilePathSuffix == "" { 84 s.FilePathSuffix = "regions.yaml" 85 } 86 87 var err error 88 // FIXME: figure out how we can fill the logger here from autowire - allowing detailed database 89 // logging when needed 90 s.conn, err = dbr.Open(s.DBDriver, s.DBDSN, nil) 91 if err != nil { 92 return err 93 } 94 95 if s.FileSystem != nil { 96 pdir := path.Dir(path.Join(s.FilePathPrefix, s.FilePathSuffix)) 97 err := s.FileSystem.MkdirAll(pdir, 0755) 98 if err != nil { 99 return err 100 } 101 s.needsFileLoad = true 102 } 103 104 return nil 105 } 106 107 func (s *DBStore) WriteDefinition(d regions.Definition) error { 108 109 if err := d.IsValid(); err != nil { 110 return err 111 } 112 113 err := s.condLoadFromFlatFile() 114 if err != nil { 115 return err 116 } 117 118 // FIXME: figure out db logging 119 sess := s.conn.NewSession(nil) 120 121 tx, err := sess.Begin() 122 if err != nil { 123 return err 124 } 125 defer tx.RollbackUnlessCommitted() 126 127 var foundDefID string 128 err = tx.Select("definition_id"). 129 From(s.TablePrefix+"region_definition"). 130 Where("definition_id=?", d.DefinitionID).LoadOne(&foundDefID) 131 if err == dbr.ErrNotFound { 132 _, err := tx.InsertInto(s.TablePrefix+"region_definition"). 133 Columns( 134 "definition_id", 135 "region_name", "sequence", "disabled", "template_name", 136 "cond_include_paths", "cond_exclude_paths", "cond_template", 137 "context_meta"). 138 Record(&d).Exec() 139 140 if err != nil { 141 return err 142 } 143 foundDefID = d.DefinitionID 144 145 } else if err != nil { 146 return err 147 } 148 149 if foundDefID != d.DefinitionID { 150 return fmt.Errorf("selected wrong definition ID from database, should be impossible - is column case insensitive?") 151 } 152 153 _, err = tx.Update(s.TablePrefix+"region_definition"). 154 Set("region_name", d.RegionName). 155 Set("sequence", d.Sequence). 156 Set("disabled", d.Disabled). 157 Set("template_name", d.TemplateName). 158 Set("cond_include_paths", d.CondIncludePaths). 159 Set("cond_exclude_paths", d.CondExcludePaths). 160 Set("cond_template", d.CondTemplate). 161 Set("context_meta", d.ContextMeta). 162 Exec() 163 if err != nil { 164 return err 165 } 166 167 err = tx.Commit() 168 if err != nil { 169 return err 170 } 171 172 if s.FileSystem != nil { 173 return s.SaveToFlatFile() 174 } 175 return nil 176 } 177 178 func (s *DBStore) DeleteDefinition(defintionID string) error { 179 180 err := s.condLoadFromFlatFile() 181 if err != nil { 182 return err 183 } 184 185 sess := s.conn.NewSession(nil) 186 _, err = sess.DeleteFrom(s.TablePrefix+"region_definition").Where("definition_id=?", defintionID).Exec() 187 if err != nil { 188 return err 189 } 190 if s.FileSystem != nil { 191 return s.SaveToFlatFile() 192 } 193 return nil 194 } 195 196 func (s *DBStore) AllDefinitions() (regions.DefinitionList, error) { 197 198 err := s.condLoadFromFlatFile() 199 if err != nil { 200 return nil, err 201 } 202 203 var ret regions.DefinitionList 204 sess := s.conn.NewSession(nil) 205 _, err = sess.Select("*").From(s.TablePrefix + "region_definition").OrderAsc("definition_id").Load(&ret) 206 if err != nil { 207 return nil, err 208 } 209 return ret, nil 210 } 211 212 // load from file if needsFileLoad is set 213 func (s *DBStore) condLoadFromFlatFile() error { 214 s.rwmu.RLock() 215 needsFileLoad := s.needsFileLoad 216 s.rwmu.RUnlock() 217 if needsFileLoad { 218 return s.LoadFromFlatFile() 219 } 220 return nil 221 } 222 223 // LoadFromFlatFile reads the contents of the flat file and loads into the database. 224 func (s *DBStore) LoadFromFlatFile() error { 225 s.rwmu.Lock() 226 defer s.rwmu.Unlock() 227 s.needsFileLoad = false 228 229 f, err := s.FileSystem.Open(path.Join(s.FilePathPrefix, s.FilePathSuffix)) 230 if err != nil { 231 // need to think about this more, but for now it is not an error if the file does yet exist 232 if os.IsNotExist(err) { 233 return nil 234 } 235 return err 236 } 237 defer f.Close() 238 239 b, err := ioutil.ReadAll(f) 240 if err != nil { 241 return err 242 } 243 244 var defs regions.DefinitionList 245 err = yaml.Unmarshal(b, &defs) 246 if err != nil { 247 return err 248 } 249 250 sess := s.conn.NewSession(nil) 251 tx, err := sess.Begin() 252 if err != nil { 253 return err 254 } 255 defer tx.RollbackUnlessCommitted() 256 257 // remove whatever's there 258 _, err = tx.DeleteFrom(s.TablePrefix + "region_definition").Exec() 259 if err != nil { 260 return err 261 } 262 263 // TODO: batch this so we insert a bunch at a time, might perform better 264 for _, def := range defs { 265 _, err := tx.InsertInto(s.TablePrefix+"region_definition"). 266 Columns( 267 "region_name", "sequence", "disabled", "template_name", 268 "cond_include_paths", "cond_exclude_paths", "cond_template", 269 "context_meta"). 270 Record(&def).Exec() 271 if err != nil { 272 return err 273 } 274 } 275 276 return nil 277 } 278 279 // SaveToFlatFile writes the contents of the database to the flat file. 280 func (s *DBStore) SaveToFlatFile() error { 281 282 s.rwmu.Lock() 283 defer s.rwmu.Unlock() 284 s.needsFileLoad = false 285 286 var defs regions.DefinitionList 287 sess := s.conn.NewSession(nil) 288 _, err := sess.Select("*").From(s.TablePrefix + "region_definition").OrderAsc("definition_id").Load(&defs) 289 if err != nil { 290 return err 291 } 292 293 b, err := yaml.Marshal(&defs) 294 if err != nil { 295 return err 296 } 297 298 p := path.Join(s.FilePathPrefix, s.FilePathSuffix) 299 f, err := s.FileSystem.OpenFile(p, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) 300 if err != nil { 301 return err 302 } 303 defer f.Close() 304 305 _, err = f.Write(b) 306 if err != nil { 307 return err 308 } 309 310 return nil 311 312 }