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}