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
18var migrations *migrate.PackrMigrationSource
19
20func init() {
21 migrations = &migrate.PackrMigrationSource{
22 Box: packr.New("hermez-db-migrations", "./migrations"),
23 }
24 ms, err := migrations.FindMigrations()
25 if err != nil {
26 panic(err)
27 }
28 if len(ms) == 0 {
29 panic(fmt.Errorf("no SQL migrations found"))
30 }
31}
32
33// MigrationsUp runs the SQL migrations Up
34func MigrationsUp(db *sql.DB) error {
35 nMigrations, err := migrate.Exec(db, "postgres", migrations, migrate.Up)
36 if err != nil {
37 return tracerr.Wrap(err)
38 }
39 log.Info("successfully ran ", nMigrations, " migrations Up")
40 return nil
41}
42
43// MigrationsDown runs the SQL migrations Down
44func MigrationsDown(db *sql.DB) error {
45 nMigrations, err := migrate.Exec(db, "postgres", migrations, migrate.Down)
46 if err != nil {
47 return tracerr.Wrap(err)
48 }
49 log.Info("successfully ran ", nMigrations, " migrations Down")
50 return nil
51}
52
53// ConnectSQLDB connects to the SQL DB
54func ConnectSQLDB(port int, host, user, password, name string) (*sqlx.DB, error) {
55 // Init meddler
56 initMeddler()
57 meddler.Default = meddler.PostgreSQL
58 // Stablish connection
59 psqlconn := fmt.Sprintf(
60 "host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
61 host,
62 port,
63 user,
64 password,
65 name,
66 )
67 db, err := sqlx.Connect("postgres", psqlconn)
68 if err != nil {
69 return nil, tracerr.Wrap(err)
70 }
71 return db, nil
72}
73
74// InitSQLDB runs migrations and registers meddlers
75func InitSQLDB(port int, host, user, password, name string) (*sqlx.DB, error) {
76 db, err := ConnectSQLDB(port, host, user, password, name)
77 if err != nil {
78 return nil, tracerr.Wrap(err)
79 }
80 // Run DB migrations
81 if err := MigrationsUp(db.DB); err != nil {
82 return nil, tracerr.Wrap(err)
83 }
84 return db, nil
85}
86
87// initMeddler registers tags to be used to read/write from SQL DBs using meddler
88func initMeddler() {
89 meddler.Register("bigint", BigIntMeddler{})
90 meddler.Register("bigintnull", BigIntNullMeddler{})
91}
92
93// BulkInsert performs a bulk insert with a single statement into the specified table. Example:
94// `db.BulkInsert(myDB, "INSERT INTO block (eth_block_num, timestamp, hash) VALUES %s", blocks[:])`
95// Note that all the columns must be specified in the query, and they must be
96// in the same order as in the table.
97// Note that the fields in the structs need to be defined in the same order as
98// in the table columns.
99func BulkInsert(db meddler.DB, q string, args interface{}) error {
100 arrayValue := reflect.ValueOf(args)
101 arrayLen := arrayValue.Len()
102 valueStrings := make([]string, 0, arrayLen)
103 var arglist = make([]interface{}, 0)
104 for i := 0; i < arrayLen; i++ {
105 arg := arrayValue.Index(i).Addr().Interface()
106 elemArglist, err := meddler.Default.Values(arg, true)
107 if err != nil {
108 return tracerr.Wrap(err)
109 }
110 arglist = append(arglist, elemArglist...)
111 value := "("
112 for j := 0; j < len(elemArglist); j++ {
113 value += fmt.Sprintf("$%d, ", i*len(elemArglist)+j+1)
114 }
115 value = value[:len(value)-2] + ")"
116 valueStrings = append(valueStrings, value)
117 }
118 stmt := fmt.Sprintf(q, strings.Join(valueStrings, ","))
119 _, err := db.Exec(stmt, arglist...)
120 return tracerr.Wrap(err)
121}
122
123// BigIntMeddler encodes or decodes the field value to or from JSON
124type BigIntMeddler struct{}
125
126// PreRead is called before a Scan operation for fields that have the BigIntMeddler
127func (b BigIntMeddler) PreRead(fieldAddr interface{}) (scanTarget interface{}, err error) {
128 // give a pointer to a byte buffer to grab the raw data
129 return new(string), nil
130}
131
132// PostRead is called after a Scan operation for fields that have the BigIntMeddler
133func (b BigIntMeddler) PostRead(fieldPtr, scanTarget interface{}) error {
134 ptr := scanTarget.(*string)
135 if ptr == nil {
136 return tracerr.Wrap(fmt.Errorf("BigIntMeddler.PostRead: nil pointer"))
137 }
138 field := fieldPtr.(**big.Int)
139 *field = new(big.Int).SetBytes([]byte(*ptr))
140 return nil
141}
142
143// PreWrite is called before an Insert or Update operation for fields that have the BigIntMeddler
144func (b BigIntMeddler) PreWrite(fieldPtr interface{}) (saveValue interface{}, err error) {
145 field := fieldPtr.(*big.Int)
146
147 return field.Bytes(), nil
148}
149
150// BigIntNullMeddler encodes or decodes the field value to or from JSON
151type BigIntNullMeddler struct{}
152
153// PreRead is called before a Scan operation for fields that have the BigIntNullMeddler
154func (b BigIntNullMeddler) PreRead(fieldAddr interface{}) (scanTarget interface{}, err error) {
155 return &fieldAddr, nil
156}
157
158// PostRead is called after a Scan operation for fields that have the BigIntNullMeddler
159func (b BigIntNullMeddler) PostRead(fieldPtr, scanTarget interface{}) error {
160 field := fieldPtr.(**big.Int)
161 ptrPtr := scanTarget.(*interface{})
162 if *ptrPtr == nil {
163 // null column, so set target to be zero value
164 *field = nil
165 return nil
166 }
167 // not null
168 ptr := (*ptrPtr).([]byte)
169 if ptr == nil {
170 return tracerr.Wrap(fmt.Errorf("BigIntMeddler.PostRead: nil pointer"))
171 }
172 *field = new(big.Int).SetBytes(ptr)
173 return nil
174}
175
176// PreWrite is called before an Insert or Update operation for fields that have the BigIntNullMeddler
177func (b BigIntNullMeddler) PreWrite(fieldPtr interface{}) (saveValue interface{}, err error) {
178 field := fieldPtr.(*big.Int)
179 if field == nil {
180 return nil, nil
181 }
182 return field.Bytes(), nil
183}
184
185// SliceToSlicePtrs converts any []Foo to []*Foo
186func SliceToSlicePtrs(slice interface{}) interface{} {
187 v := reflect.ValueOf(slice)
188 vLen := v.Len()
189 typ := v.Type().Elem()
190 res := reflect.MakeSlice(reflect.SliceOf(reflect.PtrTo(typ)), vLen, vLen)
191 for i := 0; i < vLen; i++ {
192 res.Index(i).Set(v.Index(i).Addr())
193 }
194 return res.Interface()
195}
196
197// SlicePtrsToSlice converts any []*Foo to []Foo
198func SlicePtrsToSlice(slice interface{}) interface{} {
199 v := reflect.ValueOf(slice)
200 vLen := v.Len()
201 typ := v.Type().Elem().Elem()
202 res := reflect.MakeSlice(reflect.SliceOf(typ), vLen, vLen)
203 for i := 0; i < vLen; i++ {
204 res.Index(i).Set(v.Index(i).Elem())
205 }
206 return res.Interface()
207}
208
209// Rollback an sql transaction, and log the error if it's not nil
210func Rollback(txn *sqlx.Tx) {
211 if err := txn.Rollback(); err != nil {
212 log.Errorw("Rollback", "err", err)
213 }
214}
215
216// RowsClose close the rows of an sql query, and log the errir if it's not nil
217func RowsClose(rows *sql.Rows) {
218 if err := rows.Close(); err != nil {
219 log.Errorw("rows.Close", "err", err)
220 }
221}