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