arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/db/utils.go
db/utils.goRaw
  1package db
  2
  3import (
  4	"database/sql"
  5	"encoding/base64"
  6	"fmt"
  7	"math/big"
  8	"reflect"
  9	"strings"
 10
 11	"github.com/gobuffalo/packr/v2"
 12	"github.com/hermeznetwork/hermez-node/log"
 13	"github.com/jmoiron/sqlx"
 14	migrate "github.com/rubenv/sql-migrate"
 15	"github.com/russross/meddler"
 16)
 17
 18// InitSQLDB runs migrations and registers meddlers
 19func InitSQLDB(port int, host, user, password, name string) (*sqlx.DB, error) {
 20	// Init meddler
 21	initMeddler()
 22	meddler.Default = meddler.PostgreSQL
 23	// Stablish connection
 24	psqlconn := fmt.Sprintf(
 25		"host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
 26		host,
 27		port,
 28		user,
 29		password,
 30		name,
 31	)
 32	db, err := sqlx.Connect("postgres", psqlconn)
 33	if err != nil {
 34		return nil, err
 35	}
 36	// Run DB migrations
 37	migrations := &migrate.PackrMigrationSource{
 38		Box: packr.New("hermez-db-migrations", "./migrations"),
 39	}
 40	nMigrations, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up)
 41	if err != nil {
 42		return nil, err
 43	}
 44	log.Info("successfully ran ", nMigrations, " migrations")
 45	return db, nil
 46}
 47
 48// initMeddler registers tags to be used to read/write from SQL DBs using meddler
 49func initMeddler() {
 50	meddler.Register("bigint", BigIntMeddler{})
 51	meddler.Register("bigintnull", BigIntNullMeddler{})
 52}
 53
 54// BulkInsert performs a bulk insert with a single statement into the specified table.  Example:
 55// `db.BulkInsert(myDB, "INSERT INTO block (eth_block_num, timestamp, hash) VALUES %s", blocks[:])`
 56// Note that all the columns must be specified in the query, and they must be
 57// in the same order as in the table.
 58// Note that the fields in the structs need to be defined in the same order as
 59// in the table columns.
 60func BulkInsert(db meddler.DB, q string, args interface{}) error {
 61	arrayValue := reflect.ValueOf(args)
 62	arrayLen := arrayValue.Len()
 63	valueStrings := make([]string, 0, arrayLen)
 64	var arglist = make([]interface{}, 0)
 65	for i := 0; i < arrayLen; i++ {
 66		arg := arrayValue.Index(i).Addr().Interface()
 67		elemArglist, err := meddler.Default.Values(arg, true)
 68		if err != nil {
 69			return err
 70		}
 71		arglist = append(arglist, elemArglist...)
 72		value := "("
 73		for j := 0; j < len(elemArglist); j++ {
 74			value += fmt.Sprintf("$%d, ", i*len(elemArglist)+j+1)
 75		}
 76		value = value[:len(value)-2] + ")"
 77		valueStrings = append(valueStrings, value)
 78	}
 79	stmt := fmt.Sprintf(q, strings.Join(valueStrings, ","))
 80	_, err := db.Exec(stmt, arglist...)
 81	return err
 82}
 83
 84// BigIntMeddler encodes or decodes the field value to or from JSON
 85type BigIntMeddler struct{}
 86
 87// PreRead is called before a Scan operation for fields that have the BigIntMeddler
 88func (b BigIntMeddler) PreRead(fieldAddr interface{}) (scanTarget interface{}, err error) {
 89	// give a pointer to a byte buffer to grab the raw data
 90	return new(string), nil
 91}
 92
 93// PostRead is called after a Scan operation for fields that have the BigIntMeddler
 94func (b BigIntMeddler) PostRead(fieldPtr, scanTarget interface{}) error {
 95	ptr := scanTarget.(*string)
 96	if ptr == nil {
 97		return fmt.Errorf("BigIntMeddler.PostRead: nil pointer")
 98	}
 99
100	data, err := base64.StdEncoding.DecodeString(*ptr)
101	if err != nil {
102		return fmt.Errorf("big.Int decode error: %v", err)
103	}
104	field := fieldPtr.(**big.Int)
105	*field = new(big.Int).SetBytes(data)
106
107	return nil
108}
109
110// PreWrite is called before an Insert or Update operation for fields that have the BigIntMeddler
111func (b BigIntMeddler) PreWrite(fieldPtr interface{}) (saveValue interface{}, err error) {
112	field := fieldPtr.(*big.Int)
113
114	str := base64.StdEncoding.EncodeToString(field.Bytes())
115
116	return str, nil
117}
118
119// BigIntNullMeddler encodes or decodes the field value to or from JSON
120type BigIntNullMeddler struct{}
121
122// PreRead is called before a Scan operation for fields that have the BigIntNullMeddler
123func (b BigIntNullMeddler) PreRead(fieldAddr interface{}) (scanTarget interface{}, err error) {
124	return &fieldAddr, nil
125}
126
127// PostRead is called after a Scan operation for fields that have the BigIntNullMeddler
128func (b BigIntNullMeddler) PostRead(fieldPtr, scanTarget interface{}) error {
129	sv := reflect.ValueOf(scanTarget)
130	if sv.Elem().IsNil() {
131		// null column, so set target to be zero value
132		fv := reflect.ValueOf(fieldPtr)
133		fv.Elem().Set(reflect.Zero(fv.Elem().Type()))
134		return nil
135	}
136	// not null
137	encoded := new([]byte)
138	refEnc := reflect.ValueOf(encoded)
139	refEnc.Elem().Set(sv.Elem().Elem())
140	data, err := base64.StdEncoding.DecodeString(string(*encoded))
141	if err != nil {
142		return fmt.Errorf("big.Int decode error: %v", err)
143	}
144	field := fieldPtr.(**big.Int)
145	*field = new(big.Int).SetBytes(data)
146	return nil
147}
148
149// PreWrite is called before an Insert or Update operation for fields that have the BigIntNullMeddler
150func (b BigIntNullMeddler) PreWrite(fieldPtr interface{}) (saveValue interface{}, err error) {
151	field := fieldPtr.(*big.Int)
152	if field == nil {
153		return nil, nil
154	}
155	return base64.StdEncoding.EncodeToString(field.Bytes()), nil
156}
157
158// SliceToSlicePtrs converts any []Foo to []*Foo
159func SliceToSlicePtrs(slice interface{}) interface{} {
160	v := reflect.ValueOf(slice)
161	vLen := v.Len()
162	typ := v.Type().Elem()
163	res := reflect.MakeSlice(reflect.SliceOf(reflect.PtrTo(typ)), vLen, vLen)
164	for i := 0; i < vLen; i++ {
165		res.Index(i).Set(v.Index(i).Addr())
166	}
167	return res.Interface()
168}
169
170// SlicePtrsToSlice converts any []*Foo to []Foo
171func SlicePtrsToSlice(slice interface{}) interface{} {
172	v := reflect.ValueOf(slice)
173	vLen := v.Len()
174	typ := v.Type().Elem().Elem()
175	res := reflect.MakeSlice(reflect.SliceOf(typ), vLen, vLen)
176	for i := 0; i < vLen; i++ {
177		res.Index(i).Set(v.Index(i).Elem())
178	}
179	return res.Interface()
180}
181
182// Pagination give information on the items of a query
183type Pagination struct {
184	TotalItems        uint64 `json:"totalItems"`
185	FirstItem         uint64 `json:"firstItem"`
186	LastItem          uint64 `json:"lastItem"`
187	FirstReturnedItem uint64 `json:"-"`
188	LastReturnedItem  uint64 `json:"-"`
189}
190
191// Paginationer is an interface that allows getting pagination info on any struct
192type Paginationer interface {
193	GetPagination() *Pagination
194	Len() int
195}
196
197// Rollback an sql transaction, and log the error if it's not nil
198func Rollback(txn *sql.Tx) {
199	err := txn.Rollback()
200	if err != nil {
201		log.Errorw("Rollback", "err", err)
202	}
203}