github.com/jbking/gohan@v0.0.0-20151217002006-b41ccf1c2a96/db/sql/sql.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 sql
    17  
    18  import (
    19  	"encoding/json"
    20  	"fmt"
    21  	"strconv"
    22  	"strings"
    23  	"time"
    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/jmoiron/sqlx"
    30  	sq "github.com/lann/squirrel"
    31  	// DB import
    32  	_ "github.com/go-sql-driver/mysql"
    33  	_ "github.com/mattn/go-sqlite3"
    34  )
    35  
    36  const retryDB = 50
    37  const retryDBWait = 10
    38  
    39  const (
    40  	configVersionColumnName   = "config_version"
    41  	stateVersionColumnName    = "state_version"
    42  	stateErrorColumnName      = "state_error"
    43  	stateColumnName           = "state"
    44  	stateMonitoringColumnName = "state_monitoring"
    45  )
    46  
    47  //DB is sql implementation of DB
    48  type DB struct {
    49  	sqlType, connectionString string
    50  	handlers                  map[string]propertyHandler
    51  	DB                        *sqlx.DB
    52  }
    53  
    54  //Transaction is sql implementation of Transaction
    55  type Transaction struct {
    56  	transaction *sqlx.Tx
    57  	db          *DB
    58  	closed      bool
    59  }
    60  
    61  //NewDB constructor
    62  func NewDB() *DB {
    63  	handlers := make(map[string]propertyHandler)
    64  	//TODO(nati) dynamic configuration
    65  	handlers["string"] = &stringHandler{}
    66  	handlers["number"] = &numberHandler{}
    67  	handlers["integer"] = &numberHandler{}
    68  	handlers["object"] = &jsonHandler{}
    69  	handlers["array"] = &jsonHandler{}
    70  	handlers["boolean"] = &boolHandler{}
    71  	return &DB{handlers: handlers}
    72  }
    73  
    74  //propertyHandler for each propertys
    75  type propertyHandler interface {
    76  	encode(*schema.Property, interface{}) (interface{}, error)
    77  	decode(*schema.Property, interface{}) (interface{}, error)
    78  	dataType(*schema.Property) string
    79  }
    80  
    81  type defaultHandler struct {
    82  }
    83  
    84  func (handler *defaultHandler) encode(property *schema.Property, data interface{}) (interface{}, error) {
    85  	return data, nil
    86  }
    87  
    88  func (handler *defaultHandler) decode(property *schema.Property, data interface{}) (interface{}, error) {
    89  	return data, nil
    90  }
    91  
    92  func (handler *defaultHandler) dataType(property *schema.Property) (res string) {
    93  	// TODO(marcin) extend types for schema. Here is pretty ugly guessing
    94  	if property.ID == "id" || property.Relation != "" || property.Unique {
    95  		res = "varchar(255)"
    96  	} else {
    97  		res = "text"
    98  	}
    99  	return
   100  }
   101  
   102  type stringHandler struct {
   103  	defaultHandler
   104  }
   105  
   106  func (handler *stringHandler) encode(property *schema.Property, data interface{}) (interface{}, error) {
   107  	return data, nil
   108  }
   109  
   110  func (handler *stringHandler) decode(property *schema.Property, data interface{}) (interface{}, error) {
   111  	if bytes, ok := data.([]byte); ok {
   112  		return string(bytes), nil
   113  	}
   114  	return data, nil
   115  }
   116  
   117  type boolHandler struct{}
   118  
   119  func (handler *boolHandler) encode(property *schema.Property, data interface{}) (interface{}, error) {
   120  	return data, nil
   121  }
   122  
   123  func (handler *boolHandler) decode(property *schema.Property, data interface{}) (res interface{}, err error) {
   124  	// different SQL drivers encode result with different type
   125  	// so we need to do manual checks
   126  	switch t := data.(type) {
   127  	default:
   128  		err = fmt.Errorf("unknown type %T", t)
   129  		return
   130  	case []uint8: // mysql
   131  		res, err = strconv.ParseUint(string(data.([]uint8)), 10, 64)
   132  		res = (res.(uint64) != 0)
   133  	case int64: //apparently also mysql
   134  		res = (data.(int64) != 0)
   135  	case bool: // sqlite3
   136  		res = data
   137  	}
   138  	return
   139  }
   140  
   141  func (handler *boolHandler) dataType(property *schema.Property) string {
   142  	return "boolean"
   143  }
   144  
   145  type numberHandler struct{}
   146  
   147  func (handler *numberHandler) encode(property *schema.Property, data interface{}) (interface{}, error) {
   148  	return data, nil
   149  }
   150  
   151  func (handler *numberHandler) decode(property *schema.Property, data interface{}) (res interface{}, err error) {
   152  	// different SQL drivers encode result with different type
   153  	// so we need to do manual checks
   154  	switch t := data.(type) {
   155  	default:
   156  		err = fmt.Errorf("unknown type %T", t)
   157  		return
   158  	case []uint8: // mysql
   159  		res, err = strconv.ParseUint(string(data.([]uint8)), 10, 64)
   160  	case int64: // sqlite3
   161  		res = uint64(data.(int64))
   162  	}
   163  	return
   164  }
   165  
   166  func (handler *numberHandler) dataType(property *schema.Property) string {
   167  	return "numeric"
   168  }
   169  
   170  type jsonHandler struct {
   171  }
   172  
   173  func (handler *jsonHandler) encode(property *schema.Property, data interface{}) (interface{}, error) {
   174  	bytes, err := json.Marshal(data)
   175  	//TODO(nati) should handle encoding err
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  	return bytes, nil
   180  }
   181  
   182  func (handler *jsonHandler) decode(property *schema.Property, data interface{}) (interface{}, error) {
   183  	if bytes, ok := data.([]byte); ok {
   184  		var ret interface{}
   185  		err := json.Unmarshal(bytes, &ret)
   186  		return ret, err
   187  	}
   188  	return data, nil
   189  }
   190  
   191  func (handler *jsonHandler) dataType(property *schema.Property) string {
   192  	return "text"
   193  }
   194  
   195  func quote(str string) string {
   196  	return fmt.Sprintf("`%s`", str)
   197  }
   198  
   199  //Connect connec to the db
   200  func (db *DB) Connect(sqlType, conn string) (err error) {
   201  	db.sqlType = sqlType
   202  	db.connectionString = conn
   203  	db.DB, err = sqlx.Open(db.sqlType, db.connectionString)
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	if db.sqlType == "sqlite3" {
   209  		db.DB.Exec("PRAGMA foreign_keys = ON;")
   210  	}
   211  
   212  	for i := 0; i < retryDB; i++ {
   213  		err = db.DB.Ping()
   214  		if err == nil {
   215  			return nil
   216  		}
   217  		time.Sleep(retryDBWait * time.Second)
   218  		log.Info("Retrying db connection... (%s)", err)
   219  	}
   220  
   221  	return fmt.Errorf("Failed to connect db")
   222  }
   223  
   224  //Begin starts new transaction
   225  func (db *DB) Begin() (transaction.Transaction, error) {
   226  	transaction, err := db.DB.Beginx()
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  	return &Transaction{
   231  		db:          db,
   232  		transaction: transaction,
   233  		closed:      false,
   234  	}, nil
   235  }
   236  
   237  //GenTableDef generates table create sql
   238  func (db *DB) GenTableDef(s *schema.Schema, cascade bool) string {
   239  	schemaManager := schema.GetManager()
   240  	var cols []string
   241  	var relations []string
   242  	cascadeString := ""
   243  	if cascade {
   244  		cascadeString = "on delete cascade"
   245  	}
   246  	for _, property := range s.Properties {
   247  		handler := db.handlers[property.Type]
   248  		dataType := property.SQLType
   249  		if db.sqlType == "sqlite3" {
   250  			dataType = strings.Replace(dataType, "auto_increment", "autoincrement", 1)
   251  		}
   252  		if dataType == "" {
   253  			dataType = handler.dataType(&property)
   254  			if property.ID == "id" {
   255  				dataType += " primary key"
   256  			} else {
   257  				if property.Nullable {
   258  					dataType += " null"
   259  				} else {
   260  					dataType += " not null"
   261  				}
   262  				if property.Unique {
   263  					dataType += " unique"
   264  				}
   265  			}
   266  		}
   267  		sql := "`" + property.ID + "`" + dataType
   268  
   269  		cols = append(cols, sql)
   270  		if property.Relation != "" {
   271  			foreignSchema, _ := schemaManager.Schema(property.Relation)
   272  			if foreignSchema != nil {
   273  				relations = append(relations, fmt.Sprintf("foreign key(`%s`) REFERENCES `%s`(id) %s",
   274  					property.ID, foreignSchema.GetDbTableName(), cascadeString))
   275  			}
   276  		}
   277  	}
   278  	if s.Parent != "" {
   279  		foreignSchema, _ := schemaManager.Schema(s.Parent)
   280  		relations = append(relations, fmt.Sprintf("foreign key(`%s_id`) REFERENCES `%s`(id) %s",
   281  			s.Parent, foreignSchema.GetDbTableName(), cascadeString))
   282  	}
   283  	if s.StateVersioning() {
   284  		cols = append(cols, quote(configVersionColumnName)+"int not null default 1")
   285  		cols = append(cols, quote(stateVersionColumnName)+"int not null default 0")
   286  		cols = append(cols, quote(stateErrorColumnName)+"text not null default ''")
   287  		cols = append(cols, quote(stateColumnName)+"text not null default ''")
   288  		cols = append(cols, quote(stateMonitoringColumnName)+"text not null default ''")
   289  	}
   290  	cols = append(cols, relations...)
   291  	tableSQL := fmt.Sprintf("create table `%s` (%s);\n", s.GetDbTableName(), strings.Join(cols, ","))
   292  	log.Debug("Creating table: " + tableSQL)
   293  	return tableSQL
   294  }
   295  
   296  //RegisterTable creates table in the db
   297  func (db *DB) RegisterTable(s *schema.Schema, cascade bool) error {
   298  	_, err := db.DB.Exec(db.GenTableDef(s, cascade))
   299  	return err
   300  }
   301  
   302  //DropTable drop table definition
   303  func (db *DB) DropTable(s *schema.Schema) error {
   304  	sql := fmt.Sprintf("drop table if exists %s\n", quote(s.GetDbTableName()))
   305  	_, err := db.DB.Exec(sql)
   306  	return err
   307  }
   308  
   309  func escapeID(ID string) string {
   310  	return strings.Replace(ID, "-", "_escape_", -1)
   311  }
   312  
   313  func logQuery(sql string, args ...interface{}) {
   314  	sqlFormat := strings.Replace(sql, "?", "%s", -1)
   315  	query := fmt.Sprintf(sqlFormat, args...)
   316  	log.Debug("Executing SQL query '%s'", query)
   317  }
   318  
   319  // Exec executes sql in transaction
   320  func (tx *Transaction) Exec(sql string, args ...interface{}) error {
   321  	logQuery(sql, args...)
   322  	_, err := tx.transaction.Exec(sql, args...)
   323  	return err
   324  }
   325  
   326  //Create create resource in the db
   327  func (tx *Transaction) Create(resource *schema.Resource) error {
   328  	var cols []string
   329  	var values []interface{}
   330  	db := tx.db
   331  	s := resource.Schema()
   332  	data := resource.Data()
   333  	q := sq.Insert(quote(s.GetDbTableName()))
   334  	for _, attr := range s.Properties {
   335  		//TODO(nati) support optional value
   336  		if _, ok := data[attr.ID]; ok {
   337  			handler := db.handler(&attr)
   338  			cols = append(cols, quote(attr.ID))
   339  			encoded, err := handler.encode(&attr, data[attr.ID])
   340  			if err != nil {
   341  				return fmt.Errorf("SQL Create encoding error: %s", err)
   342  			}
   343  			values = append(values, encoded)
   344  		}
   345  	}
   346  	q = q.Columns(cols...).Values(values...)
   347  	sql, args, err := q.ToSql()
   348  	if err != nil {
   349  		return err
   350  	}
   351  	return tx.Exec(sql, args...)
   352  }
   353  
   354  func (tx *Transaction) updateQuery(resource *schema.Resource) (sq.UpdateBuilder, error) {
   355  	s := resource.Schema()
   356  	db := tx.db
   357  	data := resource.Data()
   358  	q := sq.Update(quote(s.GetDbTableName()))
   359  	for _, attr := range s.Properties {
   360  		//TODO(nati) support optional value
   361  		if _, ok := data[attr.ID]; ok {
   362  			handler := db.handler(&attr)
   363  			encoded, err := handler.encode(&attr, data[attr.ID])
   364  			if err != nil {
   365  				return q, fmt.Errorf("SQL Update encoding error: %s", err)
   366  			}
   367  			q = q.Set(quote(attr.ID), encoded)
   368  		}
   369  	}
   370  	if s.Parent != "" {
   371  		q = q.Set(s.ParentSchemaPropertyID(), resource.ParentID())
   372  	}
   373  	return q, nil
   374  }
   375  
   376  //Update update resource in the db
   377  func (tx *Transaction) Update(resource *schema.Resource) error {
   378  	q, err := tx.updateQuery(resource)
   379  	if err != nil {
   380  		return err
   381  	}
   382  	sql, args, err := q.ToSql()
   383  	if err != nil {
   384  		return err
   385  	}
   386  	if resource.Schema().StateVersioning() {
   387  		sql += ", `" + configVersionColumnName + "` = `" + configVersionColumnName + "` + 1"
   388  	}
   389  	sql += " WHERE id = ?"
   390  	args = append(args, resource.ID())
   391  	return tx.Exec(sql, args...)
   392  }
   393  
   394  //StateUpdate update resource state
   395  func (tx *Transaction) StateUpdate(resource *schema.Resource, state *transaction.ResourceState) error {
   396  	q, err := tx.updateQuery(resource)
   397  	if err != nil {
   398  		return err
   399  	}
   400  	if resource.Schema().StateVersioning() && state != nil {
   401  		q = q.Set(quote(stateVersionColumnName), state.StateVersion)
   402  		q = q.Set(quote(stateErrorColumnName), state.Error)
   403  		q = q.Set(quote(stateColumnName), state.State)
   404  		q = q.Set(quote(stateMonitoringColumnName), state.Monitoring)
   405  	}
   406  	q = q.Where(sq.Eq{"id": resource.ID()})
   407  	sql, args, err := q.ToSql()
   408  	if err != nil {
   409  		return err
   410  	}
   411  	return tx.Exec(sql, args...)
   412  }
   413  
   414  //Delete delete resource from db
   415  func (tx *Transaction) Delete(s *schema.Schema, resourceID interface{}) error {
   416  	sql, args, err := sq.Delete(quote(s.GetDbTableName())).Where(sq.Eq{"id": resourceID}).ToSql()
   417  	if err != nil {
   418  		return err
   419  	}
   420  	return tx.Exec(sql, args...)
   421  }
   422  
   423  func (db *DB) handler(property *schema.Property) propertyHandler {
   424  	handler, ok := db.handlers[property.Type]
   425  	if ok {
   426  		return handler
   427  	}
   428  	return &defaultHandler{}
   429  }
   430  
   431  func makeColumnID(s *schema.Schema, property schema.Property) string {
   432  	return fmt.Sprintf("%s__%s", s.GetDbTableName(), property.ID)
   433  }
   434  
   435  func makeColumn(s *schema.Schema, property schema.Property) string {
   436  	return fmt.Sprintf("%s.%s", s.GetDbTableName(), quote(property.ID))
   437  }
   438  
   439  // MakeColumns generates an array that has Gohan style colmun names
   440  func MakeColumns(s *schema.Schema, join bool) []string {
   441  	var cols []string
   442  	manager := schema.GetManager()
   443  	for _, property := range s.Properties {
   444  		cols = append(cols, makeColumn(s, property)+" as "+quote(makeColumnID(s, property)))
   445  		if property.RelationProperty != "" && join {
   446  			relatedSchema, _ := manager.Schema(property.Relation)
   447  			cols = append(cols, MakeColumns(relatedSchema, true)...)
   448  		}
   449  	}
   450  	return cols
   451  }
   452  
   453  func makeStateColumns(s *schema.Schema) (cols []string) {
   454  	dbTableName := s.GetDbTableName()
   455  	cols = append(cols, dbTableName+"."+configVersionColumnName+" as "+quote(configVersionColumnName))
   456  	cols = append(cols, dbTableName+"."+stateVersionColumnName+" as "+quote(stateVersionColumnName))
   457  	cols = append(cols, dbTableName+"."+stateErrorColumnName+" as "+quote(stateErrorColumnName))
   458  	cols = append(cols, dbTableName+"."+stateColumnName+" as "+quote(stateColumnName))
   459  	cols = append(cols, dbTableName+"."+stateMonitoringColumnName+" as "+quote(stateMonitoringColumnName))
   460  	return cols
   461  }
   462  
   463  func makeJoin(s *schema.Schema, q sq.SelectBuilder) sq.SelectBuilder {
   464  	manager := schema.GetManager()
   465  	for _, property := range s.Properties {
   466  		if property.RelationProperty == "" {
   467  			continue
   468  		}
   469  		relatedSchema, _ := manager.Schema(property.Relation)
   470  		q = q.LeftJoin(
   471  			quote(relatedSchema.GetDbTableName()) + fmt.Sprintf(" on %s.%s = %s.id", s.GetDbTableName(), property.ID, relatedSchema.GetDbTableName()))
   472  		q = makeJoin(relatedSchema, q)
   473  	}
   474  	return q
   475  }
   476  
   477  func (tx *Transaction) decode(s *schema.Schema, data map[string]interface{}, resource map[string]interface{}) {
   478  	manager := schema.GetManager()
   479  	db := tx.db
   480  	for _, property := range s.Properties {
   481  		handler := db.handler(&property)
   482  		value := data[makeColumnID(s, property)]
   483  		if value != nil || property.Nullable {
   484  			decoded, err := handler.decode(&property, value)
   485  			if err != nil {
   486  				log.Error(fmt.Sprintf("SQL List decoding error: %s", err))
   487  			}
   488  			resource[property.ID] = decoded
   489  		}
   490  		if property.RelationProperty != "" {
   491  			relatedSchema, _ := manager.Schema(property.Relation)
   492  			resourceData := map[string]interface{}{}
   493  			tx.decode(relatedSchema, data, resourceData)
   494  			resource[property.RelationProperty] = resourceData
   495  		}
   496  	}
   497  }
   498  
   499  func decodeState(data map[string]interface{}, state *transaction.ResourceState) error {
   500  	var ok bool
   501  	state.ConfigVersion, ok = data[configVersionColumnName].(int64)
   502  	if !ok {
   503  		return fmt.Errorf("Wrong state column %s returned from query", configVersionColumnName)
   504  	}
   505  	state.StateVersion, ok = data[stateVersionColumnName].(int64)
   506  	if !ok {
   507  		return fmt.Errorf("Wrong state column %s returned from query", stateVersionColumnName)
   508  	}
   509  	stateError, ok := data[stateErrorColumnName].([]byte)
   510  	if !ok {
   511  		return fmt.Errorf("Wrong state column %s returned from query", stateErrorColumnName)
   512  	}
   513  	state.Error = string(stateError)
   514  	stateState, ok := data[stateColumnName].([]byte)
   515  	if !ok {
   516  		return fmt.Errorf("Wrong state column %s returned from query", stateColumnName)
   517  	}
   518  	state.State = string(stateState)
   519  	stateMonitoring, ok := data[stateMonitoringColumnName].([]byte)
   520  	if !ok {
   521  		return fmt.Errorf("Wrong state column %s returned from query", stateMonitoringColumnName)
   522  	}
   523  	state.Monitoring = string(stateMonitoring)
   524  	return nil
   525  }
   526  
   527  //List resources in the db
   528  func (tx *Transaction) List(s *schema.Schema, filter map[string]interface{}, pg *pagination.Paginator) (list []*schema.Resource, total uint64, err error) {
   529  	cols := MakeColumns(s, true)
   530  	q := sq.Select(cols...).From(quote(s.GetDbTableName()))
   531  	q = addFilterToQuery(s, q, filter, true)
   532  
   533  	if pg != nil {
   534  		property, err := s.GetPropertyByID(pg.Key)
   535  		if err == nil {
   536  			q = q.OrderBy(makeColumn(s, *property) + " " + pg.Order)
   537  			if pg.Limit > 0 {
   538  				q = q.Limit(pg.Limit)
   539  			}
   540  			if pg.Offset > 0 {
   541  				q = q.Offset(pg.Offset)
   542  			}
   543  		}
   544  	}
   545  	q = makeJoin(s, q)
   546  
   547  	sql, args, err := q.ToSql()
   548  	if err != nil {
   549  		return
   550  	}
   551  	logQuery(sql, args...)
   552  	rows, err := tx.transaction.Queryx(sql, args...)
   553  	if err != nil {
   554  		return
   555  	}
   556  	defer rows.Close()
   557  	list, err = tx.decodeRows(s, rows, list)
   558  	if err != nil {
   559  		return nil, 0, err
   560  	}
   561  	total, err = tx.count(s, filter)
   562  	return
   563  }
   564  
   565  // Query with raw sql string
   566  func (tx *Transaction) Query(s *schema.Schema, query string, arguments []interface{}) (list []*schema.Resource, err error) {
   567  	logQuery(query, arguments...)
   568  	rows, err := tx.transaction.Queryx(query, arguments...)
   569  	if err != nil {
   570  		return nil, fmt.Errorf("Failed to run query: %s", query)
   571  	}
   572  
   573  	defer rows.Close()
   574  	list, err = tx.decodeRows(s, rows, list)
   575  	if err != nil {
   576  		return nil, err
   577  	}
   578  
   579  	return
   580  }
   581  
   582  func (tx *Transaction) decodeRows(s *schema.Schema, rows *sqlx.Rows, list []*schema.Resource) ([]*schema.Resource, error) {
   583  	for rows.Next() {
   584  		resourceData := map[string]interface{}{}
   585  		data := map[string]interface{}{}
   586  		rows.MapScan(data)
   587  
   588  		var resource *schema.Resource
   589  		tx.decode(s, data, resourceData)
   590  		resource, err := schema.NewResource(s, resourceData)
   591  		if err != nil {
   592  			return nil, fmt.Errorf("Failed to decode rows")
   593  		}
   594  		list = append(list, resource)
   595  	}
   596  	return list, nil
   597  }
   598  
   599  //count count all matching resources in the db
   600  func (tx *Transaction) count(s *schema.Schema, filter map[string]interface{}) (res uint64, err error) {
   601  	q := sq.Select("Count(id) as count").From(quote(s.GetDbTableName()))
   602  	q = addFilterToQuery(s, q, filter, false)
   603  
   604  	sql, args, err := q.ToSql()
   605  	if err != nil {
   606  		return
   607  	}
   608  	result := map[string]interface{}{}
   609  	err = tx.transaction.QueryRowx(sql, args...).MapScan(result)
   610  	if err != nil {
   611  		return
   612  	}
   613  	count, _ := result["count"]
   614  	decoder := &numberHandler{}
   615  	decoded, decodeErr := decoder.decode(nil, count)
   616  	if decodeErr != nil {
   617  		err = fmt.Errorf("SQL List decoding error: %s", decodeErr)
   618  		return
   619  	}
   620  	res = decoded.(uint64)
   621  	return
   622  }
   623  
   624  //Fetch resources by ID in the db
   625  func (tx *Transaction) Fetch(s *schema.Schema, ID interface{}, tenantFilter []string) (*schema.Resource, error) {
   626  	query := map[string]interface{}{
   627  		"id": ID,
   628  	}
   629  	if tenantFilter != nil {
   630  		query["tenant_id"] = tenantFilter
   631  	}
   632  	list, _, err := tx.List(s, query, nil)
   633  	if len(list) != 1 {
   634  		return nil, fmt.Errorf("Failed to fetch %s", ID)
   635  	}
   636  	return list[0], err
   637  }
   638  
   639  //StateFetch fetches the state of the specified resource
   640  func (tx *Transaction) StateFetch(s *schema.Schema, ID interface{}, tenantFilter []string) (state transaction.ResourceState, err error) {
   641  	if !s.StateVersioning() {
   642  		err = fmt.Errorf("Schema %s does not support state versioning.", s.ID)
   643  		return
   644  	}
   645  	filter := map[string]interface{}{
   646  		"id": ID,
   647  	}
   648  	if tenantFilter != nil {
   649  		filter["tenant_id"] = tenantFilter
   650  	}
   651  	cols := makeStateColumns(s)
   652  	q := sq.Select(cols...).From(quote(s.GetDbTableName()))
   653  	q = addFilterToQuery(s, q, filter, true)
   654  
   655  	sql, args, err := q.ToSql()
   656  	if err != nil {
   657  		return
   658  	}
   659  	logQuery(sql, args...)
   660  	rows, err := tx.transaction.Queryx(sql, args...)
   661  	if err != nil {
   662  		return
   663  	}
   664  	defer rows.Close()
   665  	if !rows.Next() {
   666  		err = fmt.Errorf("No resource found")
   667  		return
   668  	}
   669  	data := map[string]interface{}{}
   670  	rows.MapScan(data)
   671  	err = decodeState(data, &state)
   672  	return
   673  }
   674  
   675  //RawTransaction returns raw transaction
   676  func (tx *Transaction) RawTransaction() *sqlx.Tx {
   677  	return tx.transaction
   678  }
   679  
   680  //Commit commits transaction
   681  func (tx *Transaction) Commit() error {
   682  	err := tx.transaction.Commit()
   683  	if err != nil {
   684  		return err
   685  	}
   686  	tx.closed = true
   687  	return nil
   688  }
   689  
   690  //Close closes connection
   691  func (tx *Transaction) Close() error {
   692  	//Rollback if it isn't commited yet
   693  	var err error
   694  	if !tx.closed {
   695  		err = tx.transaction.Rollback()
   696  		if err != nil {
   697  			return err
   698  		}
   699  		tx.closed = true
   700  	}
   701  	return nil
   702  }
   703  
   704  //Closed returns whether the transaction is closed
   705  func (tx *Transaction) Closed() bool {
   706  	return tx.closed
   707  }
   708  
   709  func addFilterToQuery(s *schema.Schema, q sq.SelectBuilder, filter map[string]interface{}, join bool) sq.SelectBuilder {
   710  	if filter == nil {
   711  		return q
   712  	}
   713  	for key, value := range filter {
   714  		property, err := s.GetPropertyByID(key)
   715  		var column string
   716  		if join {
   717  			column = makeColumn(s, *property)
   718  		} else {
   719  			column = quote(key)
   720  		}
   721  		if err != nil {
   722  			log.Notice(err.Error())
   723  			continue
   724  		}
   725  
   726  		if property.Type == "boolean" {
   727  			v := make([]bool, len(value.([]string)))
   728  			for i, j := range value.([]string) {
   729  				v[i], _ = strconv.ParseBool(j)
   730  			}
   731  			q = q.Where(sq.Eq{column: v})
   732  		} else {
   733  			q = q.Where(sq.Eq{column: value})
   734  		}
   735  	}
   736  	return q
   737  }