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/hermeznetwork/tracerr"
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, tracerr.Wrap(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, tracerr.Wrap(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 tracerr.Wrap(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 tracerr.Wrap(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 tracerr.Wrap(fmt.Errorf("BigIntMeddler.PostRead: nil pointer"))
97 }
98 field := fieldPtr.(**big.Int)
99 *field = new(big.Int).SetBytes([]byte(*ptr))
100 return nil
101}
102
103// PreWrite is called before an Insert or Update operation for fields that have the BigIntMeddler
104func (b BigIntMeddler) PreWrite(fieldPtr interface{}) (saveValue interface{}, err error) {
105 field := fieldPtr.(*big.Int)
106
107 return field.Bytes(), nil
108}
109
110// BigIntNullMeddler encodes or decodes the field value to or from JSON
111type BigIntNullMeddler struct{}
112
113// PreRead is called before a Scan operation for fields that have the BigIntNullMeddler
114func (b BigIntNullMeddler) PreRead(fieldAddr interface{}) (scanTarget interface{}, err error) {
115 return &fieldAddr, nil
116}
117
118// PostRead is called after a Scan operation for fields that have the BigIntNullMeddler
119func (b BigIntNullMeddler) PostRead(fieldPtr, scanTarget interface{}) error {
120 field := fieldPtr.(**big.Int)
121 ptrPtr := scanTarget.(*interface{})
122 if *ptrPtr == nil {
123 // null column, so set target to be zero value
124 *field = nil
125 return nil
126 }
127 // not null
128 ptr := (*ptrPtr).([]byte)
129 if ptr == nil {
130 return tracerr.Wrap(fmt.Errorf("BigIntMeddler.PostRead: nil pointer"))
131 }
132 *field = new(big.Int).SetBytes(ptr)
133 return nil
134}
135
136// PreWrite is called before an Insert or Update operation for fields that have the BigIntNullMeddler
137func (b BigIntNullMeddler) PreWrite(fieldPtr interface{}) (saveValue interface{}, err error) {
138 field := fieldPtr.(*big.Int)
139 if field == nil {
140 return nil, nil
141 }
142 return field.Bytes(), nil
143}
144
145// SliceToSlicePtrs converts any []Foo to []*Foo
146func SliceToSlicePtrs(slice interface{}) interface{} {
147 v := reflect.ValueOf(slice)
148 vLen := v.Len()
149 typ := v.Type().Elem()
150 res := reflect.MakeSlice(reflect.SliceOf(reflect.PtrTo(typ)), vLen, vLen)
151 for i := 0; i < vLen; i++ {
152 res.Index(i).Set(v.Index(i).Addr())
153 }
154 return res.Interface()
155}
156
157// SlicePtrsToSlice converts any []*Foo to []Foo
158func SlicePtrsToSlice(slice interface{}) interface{} {
159 v := reflect.ValueOf(slice)
160 vLen := v.Len()
161 typ := v.Type().Elem().Elem()
162 res := reflect.MakeSlice(reflect.SliceOf(typ), vLen, vLen)
163 for i := 0; i < vLen; i++ {
164 res.Index(i).Set(v.Index(i).Elem())
165 }
166 return res.Interface()
167}
168
169// Rollback an sql transaction, and log the error if it's not nil
170func Rollback(txn *sqlx.Tx) {
171 err := txn.Rollback()
172 if err != nil {
173 log.Errorw("Rollback", "err", err)
174 }
175}