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