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  }