github.com/mmatczuk/gohan@v0.0.0-20170206152520-30e45d9bdb69/db/file/file.go (about) 1 // Copyright (C) 2015 NTT Innovation Institute, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 // implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 package file 17 18 import ( 19 "fmt" 20 "sort" 21 "strconv" 22 23 "github.com/jmoiron/sqlx" 24 25 "github.com/cloudwan/gohan/db/pagination" 26 "github.com/cloudwan/gohan/db/transaction" 27 28 "github.com/cloudwan/gohan/schema" 29 "github.com/cloudwan/gohan/util" 30 ) 31 32 //DB is yaml implementation of DB 33 //This db backend is intended for development and test purpose only 34 type DB struct { 35 filePath string 36 data map[string]interface{} 37 } 38 39 //Transaction is yaml implementation of DB 40 //This db backend is intended for development and test purpose only 41 type Transaction struct { 42 db *DB 43 } 44 45 //NewDB constructor 46 func NewDB() *DB { 47 return &DB{} 48 } 49 50 //Connect connec to the db 51 func (db *DB) Connect(format, conn string, maxOpenConn int) error { 52 db.filePath = conn 53 db.load() 54 return nil 55 } 56 57 func (db *DB) Close() { 58 // nothing to do 59 } 60 61 //Begin connection starts new transaction 62 func (db *DB) Begin() (transaction.Transaction, error) { 63 return &Transaction{ 64 db: db, 65 }, nil 66 } 67 68 //Close connection 69 func (tx *Transaction) Close() error { 70 return nil 71 } 72 73 //Closed is unsupported in this db 74 func (tx *Transaction) Closed() bool { 75 return false 76 } 77 78 //RegisterTable register table definition 79 func (db *DB) RegisterTable(s *schema.Schema, cascade bool) error { 80 return nil 81 } 82 83 //DropTable drop table definition 84 func (db *DB) DropTable(s *schema.Schema) error { 85 return nil 86 } 87 88 func (db *DB) load() error { 89 data, err := util.LoadMap(db.filePath) 90 if err != nil { 91 db.data = map[string]interface{}{} 92 return err 93 } 94 db.data = data 95 return nil 96 } 97 98 func (db *DB) write() error { 99 return util.SaveFile(db.filePath, db.data) 100 } 101 102 func (db *DB) getTable(s *schema.Schema) []interface{} { 103 rawTable, ok := db.data[s.GetDbTableName()] 104 if ok { 105 return rawTable.([]interface{}) 106 } 107 newTable := []interface{}{} 108 db.data[s.GetDbTableName()] = newTable 109 return newTable 110 } 111 112 //Commit commits changes to db 113 //Unsupported in this db 114 func (tx *Transaction) Commit() error { 115 return nil 116 } 117 118 //Create create resource in the db 119 func (tx *Transaction) Create(resource *schema.Resource) error { 120 db := tx.db 121 db.load() 122 s := resource.Schema() 123 data := resource.Data() 124 table := db.getTable(s) 125 db.data[s.GetDbTableName()] = append(table, data) 126 db.write() 127 return nil 128 } 129 130 //Update update resource in the db 131 func (tx *Transaction) Update(resource *schema.Resource) error { 132 db := tx.db 133 db.load() 134 s := resource.Schema() 135 data := resource.Data() 136 table := db.getTable(s) 137 for _, rawDataInDB := range table { 138 dataInDB := rawDataInDB.(map[string]interface{}) 139 if dataInDB["id"] == resource.ID() { 140 for key, value := range data { 141 dataInDB[key] = value 142 } 143 } 144 } 145 db.write() 146 return nil 147 } 148 149 //StateUpdate update resource state 150 func (tx *Transaction) StateUpdate(resource *schema.Resource, _ *transaction.ResourceState) error { 151 return tx.Update(resource) 152 } 153 154 //Delete delete resource from db 155 func (tx *Transaction) Delete(s *schema.Schema, resourceID interface{}) error { 156 db := tx.db 157 db.load() 158 table := db.getTable(s) 159 newTable := []interface{}{} 160 for _, rawDataInDB := range table { 161 dataInDB := rawDataInDB.(map[string]interface{}) 162 if dataInDB["id"] != resourceID { 163 newTable = append(newTable, dataInDB) 164 } 165 } 166 db.data[s.GetDbTableName()] = newTable 167 db.write() 168 return nil 169 } 170 171 type byPaginator struct { 172 data []*schema.Resource 173 pg *pagination.Paginator 174 } 175 176 func (s byPaginator) Len() int { 177 return len(s.data) 178 } 179 func (s byPaginator) Swap(i, j int) { 180 s.data[i], s.data[j] = s.data[j], s.data[i] 181 } 182 func (s byPaginator) Less(i, j int) bool { 183 key := s.pg.Key 184 vi := s.data[i].Get(key) 185 vj := s.data[j].Get(key) 186 var less bool 187 188 switch vi.(type) { 189 case int: 190 less = vi.(int) < vj.(int) 191 case float64: 192 less = vi.(float64) < vj.(float64) 193 case string: 194 less = vi.(string) < vj.(string) 195 default: 196 panic(fmt.Sprintf("uncomparable type %T", vi)) 197 } 198 199 if s.pg.Order == pagination.DESC { 200 return !less 201 } 202 return less 203 } 204 205 //List resources in the db 206 func (tx *Transaction) List(s *schema.Schema, filter transaction.Filter, pg *pagination.Paginator) (list []*schema.Resource, total uint64, err error) { 207 db := tx.db 208 db.load() 209 table := db.getTable(s) 210 for _, rawData := range table { 211 data := rawData.(map[string]interface{}) 212 var resource *schema.Resource 213 resource, err = schema.NewResource(s, data) 214 if err != nil { 215 log.Warning("%s %s", resource, err) 216 return 217 } 218 valid := true 219 if filter != nil { 220 for key, value := range filter { 221 if data[key] == nil { 222 continue 223 } 224 property, err := s.GetPropertyByID(key) 225 if err != nil { 226 continue 227 } 228 switch value.(type) { 229 case string: 230 if property.Type == "boolean" { 231 dataBool, err1 := strconv.ParseBool(data[key].(string)) 232 valueBool, err2 := strconv.ParseBool(value.(string)) 233 if err1 != nil || err2 != nil || dataBool != valueBool { 234 valid = false 235 } 236 } else if data[key] != value { 237 valid = false 238 } 239 case []string: 240 if property.Type == "boolean" { 241 v, _ := strconv.ParseBool(data[key].(string)) 242 if !boolInSlice(v, value.([]string)) { 243 valid = false 244 } 245 } 246 if !stringInSlice(fmt.Sprintf("%v", data[key]), value.([]string)) { 247 valid = false 248 } 249 default: 250 if data[key] != value { 251 valid = false 252 } 253 } 254 } 255 } 256 if valid { 257 list = append(list, resource) 258 } 259 260 if pg != nil { 261 sort.Sort(byPaginator{list, pg}) 262 if pg.Limit > 0 { 263 list = list[:pg.Limit] 264 } 265 } 266 } 267 total = uint64(len(list)) 268 return 269 } 270 271 //Fetch resources by ID in the db 272 func (tx *Transaction) Fetch(s *schema.Schema, filter transaction.Filter) (*schema.Resource, error) { 273 list, _, err := tx.List(s, filter, nil) 274 if len(list) != 1 { 275 return nil, fmt.Errorf("Failed to fetch %s", filter) 276 } 277 return list[0], err 278 } 279 280 //StateFetch is not supported in file databases 281 func (tx *Transaction) StateFetch(s *schema.Schema, filter transaction.Filter) (state transaction.ResourceState, err error) { 282 err = fmt.Errorf("StateFetch is not supported for file databases") 283 return 284 } 285 286 //SetIsolationLevel specify transaction isolation level 287 func (tx *Transaction) SetIsolationLevel(level transaction.Type) error { 288 return nil 289 } 290 291 //RawTransaction returns raw transaction 292 func (tx *Transaction) RawTransaction() *sqlx.Tx { 293 panic("Not implemented") 294 } 295 296 // Query with raw string 297 func (tx *Transaction) Query(s *schema.Schema, query string, arguments []interface{}) (list []*schema.Resource, err error) { 298 panic("Not implemented") 299 } 300 301 // Exec executes sql in transaction 302 func (tx *Transaction) Exec(sql string, args ...interface{}) error { 303 panic("Not implemented") 304 } 305 306 func stringInSlice(a string, list []string) bool { 307 for _, b := range list { 308 if b == a { 309 return true 310 } 311 } 312 return false 313 } 314 315 func boolInSlice(a bool, list []string) bool { 316 for _, b := range list { 317 v, _ := strconv.ParseBool(b) 318 if v == a { 319 return true 320 } 321 } 322 return false 323 }