arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/db/utils.go
db/utils.goRaw
  1package db
  2
  3import (
  4	"database/sql"
  5	"fmt"
  6	"math/big"
  7	"reflect"
  8	"strings"
  9
 10	"github.com/gobuffalo/packr/v2"
 11	"github.com/hermeznetwork/hermez-node/log"
 12	"github.com/hermeznetwork/tracerr"
 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, tracerr.Wrap(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, tracerr.Wrap(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 tracerr.Wrap(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 tracerr.Wrap(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 tracerr.Wrap(fmt.Errorf("BigIntMeddler.PostRead: nil pointer"))
 98	}
 99	field := fieldPtr.(**big.Int)
100	*field = new(big.Int).SetBytes([]byte(*ptr))
101	return nil
102}
103
104// PreWrite is called before an Insert or Update operation for fields that have the BigIntMeddler
105func (b BigIntMeddler) PreWrite(fieldPtr interface{}) (saveValue interface{}, err error) {
106	field := fieldPtr.(*big.Int)
107
108	return field.Bytes(), nil
109}
110
111// BigIntNullMeddler encodes or decodes the field value to or from JSON
112type BigIntNullMeddler struct{}
113
114// PreRead is called before a Scan operation for fields that have the BigIntNullMeddler
115func (b BigIntNullMeddler) PreRead(fieldAddr interface{}) (scanTarget interface{}, err error) {
116	return &fieldAddr, nil
117}
118
119// PostRead is called after a Scan operation for fields that have the BigIntNullMeddler
120func (b BigIntNullMeddler) PostRead(fieldPtr, scanTarget interface{}) error {
121	field := fieldPtr.(**big.Int)
122	ptrPtr := scanTarget.(*interface{})
123	if *ptrPtr == nil {
124		// null column, so set target to be zero value
125		*field = nil
126		return nil
127	}
128	// not null
129	ptr := (*ptrPtr).([]byte)
130	if ptr == nil {
131		return tracerr.Wrap(fmt.Errorf("BigIntMeddler.PostRead: nil pointer"))
132	}
133	*field = new(big.Int).SetBytes(ptr)
134	return nil
135}
136
137// PreWrite is called before an Insert or Update operation for fields that have the BigIntNullMeddler
138func (b BigIntNullMeddler) PreWrite(fieldPtr interface{}) (saveValue interface{}, err error) {
139	field := fieldPtr.(*big.Int)
140	if field == nil {
141		return nil, nil
142	}
143	return field.Bytes(), nil
144}
145
146// SliceToSlicePtrs converts any []Foo to []*Foo
147func SliceToSlicePtrs(slice interface{}) interface{} {
148	v := reflect.ValueOf(slice)
149	vLen := v.Len()
150	typ := v.Type().Elem()
151	res := reflect.MakeSlice(reflect.SliceOf(reflect.PtrTo(typ)), vLen, vLen)
152	for i := 0; i < vLen; i++ {
153		res.Index(i).Set(v.Index(i).Addr())
154	}
155	return res.Interface()
156}
157
158// SlicePtrsToSlice converts any []*Foo to []Foo
159func SlicePtrsToSlice(slice interface{}) interface{} {
160	v := reflect.ValueOf(slice)
161	vLen := v.Len()
162	typ := v.Type().Elem().Elem()
163	res := reflect.MakeSlice(reflect.SliceOf(typ), vLen, vLen)
164	for i := 0; i < vLen; i++ {
165		res.Index(i).Set(v.Index(i).Elem())
166	}
167	return res.Interface()
168}
169
170// Rollback an sql transaction, and log the error if it's not nil
171func Rollback(txn *sqlx.Tx) {
172	if err := txn.Rollback(); err != nil {
173		log.Errorw("Rollback", "err", err)
174	}
175}
176
177// RowsClose close the rows of an sql query, and log the errir if it's not nil
178func RowsClose(rows *sql.Rows) {
179	if err := rows.Close(); err != nil {
180		log.Errorw("rows.Close", "err", err)
181	}
182}