github.com/status-im/status-go@v1.1.0/mailserver/mailserver_db_postgres.go (about) 1 package mailserver 2 3 import ( 4 "database/sql" 5 "errors" 6 "fmt" 7 "time" 8 9 "github.com/lib/pq" 10 11 // Import postgres driver 12 _ "github.com/lib/pq" 13 "github.com/status-im/migrate/v4" 14 "github.com/status-im/migrate/v4/database/postgres" 15 bindata "github.com/status-im/migrate/v4/source/go_bindata" 16 17 "github.com/status-im/status-go/mailserver/migrations" 18 19 "github.com/ethereum/go-ethereum/log" 20 "github.com/ethereum/go-ethereum/rlp" 21 22 "github.com/status-im/status-go/eth-node/types" 23 waku "github.com/status-im/status-go/waku/common" 24 ) 25 26 type PostgresDB struct { 27 db *sql.DB 28 name string 29 done chan struct{} 30 } 31 32 func NewPostgresDB(uri string) (*PostgresDB, error) { 33 db, err := sql.Open("postgres", uri) 34 if err != nil { 35 return nil, err 36 } 37 38 instance := &PostgresDB{ 39 db: db, 40 done: make(chan struct{}), 41 } 42 if err := instance.setup(); err != nil { 43 return nil, err 44 } 45 46 // name is used for metrics labels 47 if name, err := instance.getDBName(uri); err == nil { 48 instance.name = name 49 } 50 51 // initialize the metric value 52 instance.updateArchivedEnvelopesCount() 53 // checking count on every insert is inefficient 54 go func() { 55 for { 56 select { 57 case <-instance.done: 58 return 59 case <-time.After(time.Second * envelopeCountCheckInterval): 60 instance.updateArchivedEnvelopesCount() 61 } 62 } 63 }() 64 return instance, nil 65 } 66 67 type postgresIterator struct { 68 *sql.Rows 69 } 70 71 func (i *PostgresDB) getDBName(uri string) (string, error) { 72 query := "SELECT current_database()" 73 var dbName string 74 return dbName, i.db.QueryRow(query).Scan(&dbName) 75 } 76 77 func (i *PostgresDB) envelopesCount() (int, error) { 78 query := "SELECT count(*) FROM envelopes" 79 var count int 80 return count, i.db.QueryRow(query).Scan(&count) 81 } 82 83 func (i *PostgresDB) updateArchivedEnvelopesCount() { 84 if count, err := i.envelopesCount(); err != nil { 85 log.Warn("db query for envelopes count failed", "err", err) 86 } else { 87 archivedEnvelopesGauge.WithLabelValues(i.name).Set(float64(count)) 88 } 89 } 90 91 func (i *postgresIterator) DBKey() (*DBKey, error) { 92 var value []byte 93 var id []byte 94 if err := i.Scan(&id, &value); err != nil { 95 return nil, err 96 } 97 return &DBKey{raw: id}, nil 98 } 99 100 func (i *postgresIterator) Error() error { 101 return i.Err() 102 } 103 104 func (i *postgresIterator) Release() error { 105 return i.Close() 106 } 107 108 func (i *postgresIterator) GetEnvelopeByBloomFilter(bloom []byte) ([]byte, error) { 109 var value []byte 110 var id []byte 111 if err := i.Scan(&id, &value); err != nil { 112 return nil, err 113 } 114 115 return value, nil 116 } 117 118 func (i *postgresIterator) GetEnvelopeByTopicsMap(topics map[types.TopicType]bool) ([]byte, error) { 119 var value []byte 120 var id []byte 121 if err := i.Scan(&id, &value); err != nil { 122 return nil, err 123 } 124 125 return value, nil 126 } 127 128 func (i *PostgresDB) BuildIterator(query CursorQuery) (Iterator, error) { 129 var args []interface{} 130 131 stmtString := "SELECT id, data FROM envelopes" 132 133 var historyRange string 134 if len(query.cursor) > 0 { 135 args = append(args, query.start, query.cursor) 136 // If we have a cursor, we don't want to include that envelope in the result set 137 stmtString += " " + "WHERE id >= $1 AND id < $2" 138 historyRange = "partial" //nolint: goconst 139 } else { 140 args = append(args, query.start, query.end) 141 stmtString += " " + "WHERE id >= $1 AND id <= $2" 142 historyRange = "full" //nolint: goconst 143 } 144 145 var filterRange string 146 if len(query.topics) > 0 { 147 args = append(args, pq.Array(query.topics)) 148 stmtString += " " + "AND topic = any($3)" 149 filterRange = "partial" //nolint: goconst 150 } else { 151 stmtString += " " + fmt.Sprintf("AND bloom & b'%s'::bit(512) = bloom", toBitString(query.bloom)) 152 filterRange = "full" //nolint: goconst 153 } 154 155 // Positional argument depends on the fact whether the query uses topics or bloom filter. 156 // If topic is used, the list of topics is passed as an argument to the query. 157 // If bloom filter is used, it is included into the query statement. 158 args = append(args, query.limit) 159 stmtString += " " + fmt.Sprintf("ORDER BY ID DESC LIMIT $%d", len(args)) 160 161 stmt, err := i.db.Prepare(stmtString) 162 if err != nil { 163 return nil, err 164 } 165 166 envelopeQueriesCounter.WithLabelValues(filterRange, historyRange).Inc() 167 rows, err := stmt.Query(args...) 168 if err != nil { 169 return nil, err 170 } 171 172 return &postgresIterator{rows}, nil 173 } 174 175 func (i *PostgresDB) setup() error { 176 resources := bindata.Resource( 177 migrations.AssetNames(), 178 migrations.Asset, 179 ) 180 181 source, err := bindata.WithInstance(resources) 182 if err != nil { 183 return err 184 } 185 186 driver, err := postgres.WithInstance(i.db, &postgres.Config{}) 187 if err != nil { 188 return err 189 } 190 191 m, err := migrate.NewWithInstance( 192 "go-bindata", 193 source, 194 "postgres", 195 driver) 196 if err != nil { 197 return err 198 } 199 200 if err = m.Up(); err != migrate.ErrNoChange { 201 return err 202 } 203 204 return nil 205 } 206 207 func (i *PostgresDB) Close() error { 208 select { 209 case <-i.done: 210 default: 211 close(i.done) 212 } 213 return i.db.Close() 214 } 215 216 func (i *PostgresDB) GetEnvelope(key *DBKey) ([]byte, error) { 217 statement := `SELECT data FROM envelopes WHERE id = $1` 218 219 stmt, err := i.db.Prepare(statement) 220 if err != nil { 221 return nil, err 222 } 223 defer stmt.Close() 224 225 var envelope []byte 226 227 if err = stmt.QueryRow(key.Bytes()).Scan(&envelope); err != nil { 228 return nil, err 229 } 230 231 return envelope, nil 232 } 233 234 func (i *PostgresDB) Prune(t time.Time, batch int) (int, error) { 235 var zero types.Hash 236 var emptyTopic types.TopicType 237 kl := NewDBKey(0, emptyTopic, zero) 238 ku := NewDBKey(uint32(t.Unix()), emptyTopic, zero) 239 statement := "DELETE FROM envelopes WHERE id BETWEEN $1 AND $2" 240 241 stmt, err := i.db.Prepare(statement) 242 if err != nil { 243 return 0, err 244 } 245 defer stmt.Close() 246 247 result, err := stmt.Exec(kl.Bytes(), ku.Bytes()) 248 if err != nil { 249 return 0, err 250 } 251 rows, err := result.RowsAffected() 252 if err != nil { 253 return 0, err 254 } 255 return int(rows), nil 256 } 257 258 func (i *PostgresDB) SaveEnvelope(env types.Envelope) error { 259 topic := env.Topic() 260 key := NewDBKey(env.Expiry()-env.TTL(), topic, env.Hash()) 261 rawEnvelope, err := rlp.EncodeToBytes(env.Unwrap()) 262 if err != nil { 263 log.Error(fmt.Sprintf("rlp.EncodeToBytes failed: %s", err)) 264 archivedErrorsCounter.WithLabelValues(i.name).Inc() 265 return err 266 } 267 if rawEnvelope == nil { 268 archivedErrorsCounter.WithLabelValues(i.name).Inc() 269 return errors.New("failed to encode envelope to bytes") 270 } 271 272 statement := "INSERT INTO envelopes (id, data, topic, bloom) VALUES ($1, $2, $3, B'" 273 statement += toBitString(env.Bloom()) 274 statement += "'::bit(512)) ON CONFLICT (id) DO NOTHING;" 275 stmt, err := i.db.Prepare(statement) 276 if err != nil { 277 return err 278 } 279 defer stmt.Close() 280 281 _, err = stmt.Exec( 282 key.Bytes(), 283 rawEnvelope, 284 topicToByte(topic), 285 ) 286 287 if err != nil { 288 archivedErrorsCounter.WithLabelValues(i.name).Inc() 289 return err 290 } 291 292 archivedEnvelopesGauge.WithLabelValues(i.name).Inc() 293 archivedEnvelopeSizeMeter.WithLabelValues(i.name).Observe( 294 float64(waku.EnvelopeHeaderLength + env.Size())) 295 296 return nil 297 } 298 299 func topicToByte(t types.TopicType) []byte { 300 return []byte{t[0], t[1], t[2], t[3]} 301 } 302 303 func toBitString(bloom []byte) string { 304 val := "" 305 for _, n := range bloom { 306 val += fmt.Sprintf("%08b", n) 307 } 308 return val 309 }