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  }