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