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