2021-04-13 18:52:57 +00:00
|
|
|
package persistence
|
2021-04-12 17:59:41 +00:00
|
|
|
|
|
|
|
import (
|
2023-03-09 15:48:25 +00:00
|
|
|
"context"
|
2021-04-12 17:59:41 +00:00
|
|
|
"database/sql"
|
2022-05-30 18:48:22 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
2021-11-05 14:27:30 +00:00
|
|
|
"time"
|
2021-04-12 17:59:41 +00:00
|
|
|
|
2023-08-16 01:40:00 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2022-11-09 19:53:01 +00:00
|
|
|
"github.com/waku-org/go-waku/waku/v2/protocol"
|
2024-05-03 16:07:03 +00:00
|
|
|
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_store/pb"
|
2023-02-06 22:16:20 +00:00
|
|
|
wpb "github.com/waku-org/go-waku/waku/v2/protocol/pb"
|
2022-12-09 03:08:04 +00:00
|
|
|
"github.com/waku-org/go-waku/waku/v2/timesource"
|
2024-09-25 09:15:20 +00:00
|
|
|
"github.com/waku-org/go-waku/waku/v2/utils"
|
2022-01-18 18:17:06 +00:00
|
|
|
"go.uber.org/zap"
|
2023-11-07 19:48:43 +00:00
|
|
|
"google.golang.org/protobuf/proto"
|
2021-04-12 17:59:41 +00:00
|
|
|
)
|
|
|
|
|
2023-07-07 01:38:23 +00:00
|
|
|
// MessageProvider is an interface that provides access to store/retrieve messages from a persistence store.
|
2021-10-25 19:41:08 +00:00
|
|
|
type MessageProvider interface {
|
|
|
|
GetAll() ([]StoredMessage, error)
|
2023-04-19 20:54:33 +00:00
|
|
|
Validate(env *protocol.Envelope) error
|
2022-05-30 18:48:22 +00:00
|
|
|
Put(env *protocol.Envelope) error
|
|
|
|
Query(query *pb.HistoryQuery) ([]StoredMessage, error)
|
|
|
|
MostRecentTimestamp() (int64, error)
|
2023-03-09 15:48:25 +00:00
|
|
|
Start(ctx context.Context, timesource timesource.Timesource) error
|
2021-10-25 19:41:08 +00:00
|
|
|
Stop()
|
|
|
|
}
|
|
|
|
|
2023-07-07 01:38:23 +00:00
|
|
|
// ErrInvalidCursor indicates that an invalid cursor has been passed to access store
|
2022-05-30 18:48:22 +00:00
|
|
|
var ErrInvalidCursor = errors.New("invalid cursor")
|
2023-07-07 01:38:23 +00:00
|
|
|
|
|
|
|
// ErrFutureMessage indicates that a message with timestamp in future was requested to be stored
|
2023-04-19 20:54:33 +00:00
|
|
|
var ErrFutureMessage = errors.New("message timestamp in the future")
|
2023-07-07 01:38:23 +00:00
|
|
|
|
|
|
|
// ErrMessageTooOld indicates that a message that was too old was requested to be stored.
|
2023-04-19 20:54:33 +00:00
|
|
|
var ErrMessageTooOld = errors.New("message too old")
|
2022-05-30 18:48:22 +00:00
|
|
|
|
|
|
|
// WALMode for sqlite.
|
|
|
|
const WALMode = "wal"
|
|
|
|
|
2023-04-19 20:54:33 +00:00
|
|
|
// MaxTimeVariance is the maximum duration in the future allowed for a message timestamp
|
|
|
|
const MaxTimeVariance = time.Duration(20) * time.Second
|
|
|
|
|
2021-04-22 18:49:52 +00:00
|
|
|
// DBStore is a MessageProvider that has a *sql.DB connection
|
2021-04-12 17:59:41 +00:00
|
|
|
type DBStore struct {
|
2021-10-25 19:41:08 +00:00
|
|
|
MessageProvider
|
2023-01-04 17:58:14 +00:00
|
|
|
|
|
|
|
db *sql.DB
|
2024-01-03 16:49:54 +00:00
|
|
|
migrationFn func(db *sql.DB, logger *zap.Logger) error
|
2023-01-04 17:58:14 +00:00
|
|
|
|
2023-08-16 01:40:00 +00:00
|
|
|
metrics Metrics
|
2022-12-09 03:08:04 +00:00
|
|
|
timesource timesource.Timesource
|
|
|
|
log *zap.Logger
|
2021-11-05 14:27:30 +00:00
|
|
|
|
|
|
|
maxMessages int
|
2021-11-06 13:23:58 +00:00
|
|
|
maxDuration time.Duration
|
2022-05-30 18:48:22 +00:00
|
|
|
|
2022-08-03 13:32:52 +00:00
|
|
|
enableMigrations bool
|
|
|
|
|
2023-03-09 15:48:25 +00:00
|
|
|
wg sync.WaitGroup
|
|
|
|
cancel context.CancelFunc
|
2021-04-12 17:59:41 +00:00
|
|
|
}
|
|
|
|
|
2023-07-07 01:38:23 +00:00
|
|
|
// StoredMessage is the format of the message stored in persistence store
|
2021-10-25 19:41:08 +00:00
|
|
|
type StoredMessage struct {
|
|
|
|
ID []byte
|
|
|
|
PubsubTopic string
|
2022-02-23 15:01:53 +00:00
|
|
|
ReceiverTime int64
|
2023-02-06 22:16:20 +00:00
|
|
|
Message *wpb.WakuMessage
|
2021-10-25 19:41:08 +00:00
|
|
|
}
|
|
|
|
|
2021-10-09 18:18:53 +00:00
|
|
|
// DBOption is an optional setting that can be used to configure the DBStore
|
2021-04-13 18:52:57 +00:00
|
|
|
type DBOption func(*DBStore) error
|
|
|
|
|
2021-04-22 18:49:52 +00:00
|
|
|
// WithDB is a DBOption that lets you use any custom *sql.DB with a DBStore.
|
2021-04-13 18:52:57 +00:00
|
|
|
func WithDB(db *sql.DB) DBOption {
|
|
|
|
return func(d *DBStore) error {
|
|
|
|
d.db = db
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-07 01:38:23 +00:00
|
|
|
// ConnectionPoolOptions is the options to be used for DB connection pooling
|
2022-12-10 03:08:18 +00:00
|
|
|
type ConnectionPoolOptions struct {
|
|
|
|
MaxOpenConnections int
|
|
|
|
MaxIdleConnections int
|
|
|
|
ConnectionMaxLifetime time.Duration
|
|
|
|
ConnectionMaxIdleTime time.Duration
|
|
|
|
}
|
|
|
|
|
2021-04-22 18:49:52 +00:00
|
|
|
// WithDriver is a DBOption that will open a *sql.DB connection
|
2022-12-10 03:08:18 +00:00
|
|
|
func WithDriver(driverName string, datasourceName string, connectionPoolOptions ...ConnectionPoolOptions) DBOption {
|
2021-04-13 18:52:57 +00:00
|
|
|
return func(d *DBStore) error {
|
|
|
|
db, err := sql.Open(driverName, datasourceName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-12-10 03:08:18 +00:00
|
|
|
|
|
|
|
if len(connectionPoolOptions) != 0 {
|
|
|
|
db.SetConnMaxIdleTime(connectionPoolOptions[0].ConnectionMaxIdleTime)
|
|
|
|
db.SetConnMaxLifetime(connectionPoolOptions[0].ConnectionMaxLifetime)
|
|
|
|
db.SetMaxIdleConns(connectionPoolOptions[0].MaxIdleConnections)
|
|
|
|
db.SetMaxOpenConns(connectionPoolOptions[0].MaxOpenConnections)
|
|
|
|
}
|
|
|
|
|
2021-04-13 18:52:57 +00:00
|
|
|
d.db = db
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-25 15:28:17 +00:00
|
|
|
// WithRetentionPolicy is a DBOption that specifies the max number of messages
|
|
|
|
// to be stored and duration before they're removed from the message store
|
2021-11-06 13:23:58 +00:00
|
|
|
func WithRetentionPolicy(maxMessages int, maxDuration time.Duration) DBOption {
|
2021-11-05 14:27:30 +00:00
|
|
|
return func(d *DBStore) error {
|
2021-11-06 13:23:58 +00:00
|
|
|
d.maxDuration = maxDuration
|
2021-11-05 14:27:30 +00:00
|
|
|
d.maxMessages = maxMessages
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-03 16:49:54 +00:00
|
|
|
type MigrationFn func(db *sql.DB, logger *zap.Logger) error
|
2023-08-09 17:23:44 +00:00
|
|
|
|
2023-01-04 17:58:14 +00:00
|
|
|
// WithMigrations is a DBOption used to determine if migrations should
|
|
|
|
// be executed, and what driver to use
|
2023-08-09 17:23:44 +00:00
|
|
|
func WithMigrations(migrationFn MigrationFn) DBOption {
|
2022-08-03 13:32:52 +00:00
|
|
|
return func(d *DBStore) error {
|
2023-01-04 17:58:14 +00:00
|
|
|
d.enableMigrations = true
|
|
|
|
d.migrationFn = migrationFn
|
2022-08-03 13:32:52 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-07 01:38:23 +00:00
|
|
|
// DefaultOptions returns the default DBoptions to be used.
|
2022-08-03 13:32:52 +00:00
|
|
|
func DefaultOptions() []DBOption {
|
2023-01-04 17:58:14 +00:00
|
|
|
return []DBOption{}
|
2022-08-03 13:32:52 +00:00
|
|
|
}
|
|
|
|
|
2021-04-22 18:49:52 +00:00
|
|
|
// Creates a new DB store using the db specified via options.
|
2021-11-06 13:23:58 +00:00
|
|
|
// It will create a messages table if it does not exist and
|
|
|
|
// clean up records according to the retention policy used
|
2023-08-16 01:40:00 +00:00
|
|
|
func NewDBStore(reg prometheus.Registerer, log *zap.Logger, options ...DBOption) (*DBStore, error) {
|
2021-04-13 18:52:57 +00:00
|
|
|
result := new(DBStore)
|
2022-01-18 18:17:06 +00:00
|
|
|
result.log = log.Named("dbstore")
|
2023-08-16 01:40:00 +00:00
|
|
|
result.metrics = newMetrics(reg)
|
2021-04-13 18:52:57 +00:00
|
|
|
|
2022-08-03 13:32:52 +00:00
|
|
|
optList := DefaultOptions()
|
|
|
|
optList = append(optList, options...)
|
|
|
|
|
|
|
|
for _, opt := range optList {
|
2021-11-05 14:27:30 +00:00
|
|
|
err := opt(result)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-03 13:32:52 +00:00
|
|
|
if result.enableMigrations {
|
2024-01-03 16:49:54 +00:00
|
|
|
err := result.migrationFn(result.db, log)
|
2022-08-03 13:32:52 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-04-12 17:59:41 +00:00
|
|
|
}
|
|
|
|
|
2022-12-09 03:08:04 +00:00
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2023-07-07 01:38:23 +00:00
|
|
|
// Start starts the store server functionality
|
2023-03-09 15:48:25 +00:00
|
|
|
func (d *DBStore) Start(ctx context.Context, timesource timesource.Timesource) error {
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
|
|
|
|
d.cancel = cancel
|
2022-12-09 03:08:04 +00:00
|
|
|
d.timesource = timesource
|
|
|
|
|
2023-04-19 20:54:33 +00:00
|
|
|
err := d.cleanOlderRecords(ctx)
|
2021-04-12 17:59:41 +00:00
|
|
|
if err != nil {
|
2022-12-09 03:08:04 +00:00
|
|
|
return err
|
2021-04-12 17:59:41 +00:00
|
|
|
}
|
|
|
|
|
2023-04-19 20:54:33 +00:00
|
|
|
d.wg.Add(2)
|
2023-03-09 15:48:25 +00:00
|
|
|
go d.checkForOlderRecords(ctx, 60*time.Second)
|
2023-04-19 20:54:33 +00:00
|
|
|
go d.updateMetrics(ctx)
|
2022-05-30 18:48:22 +00:00
|
|
|
|
2022-12-09 03:08:04 +00:00
|
|
|
return nil
|
2021-04-12 17:59:41 +00:00
|
|
|
}
|
|
|
|
|
2023-07-07 01:38:23 +00:00
|
|
|
func (d *DBStore) updateMetrics(ctx context.Context) {
|
2024-09-25 09:15:20 +00:00
|
|
|
defer utils.LogOnPanic()
|
2023-04-19 20:54:33 +00:00
|
|
|
ticker := time.NewTicker(5 * time.Second)
|
|
|
|
defer ticker.Stop()
|
2023-07-07 01:38:23 +00:00
|
|
|
defer d.wg.Done()
|
2023-04-19 20:54:33 +00:00
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ticker.C:
|
2023-07-07 01:38:23 +00:00
|
|
|
msgCount, err := d.Count()
|
2023-04-19 20:54:33 +00:00
|
|
|
if err != nil {
|
2023-07-07 01:38:23 +00:00
|
|
|
d.log.Error("updating store metrics", zap.Error(err))
|
2023-04-19 20:54:33 +00:00
|
|
|
} else {
|
2023-08-16 01:40:00 +00:00
|
|
|
d.metrics.RecordMessage(msgCount)
|
2023-04-19 20:54:33 +00:00
|
|
|
}
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *DBStore) cleanOlderRecords(ctx context.Context) error {
|
2022-11-25 20:54:11 +00:00
|
|
|
d.log.Info("Cleaning older records...")
|
2022-05-19 21:29:15 +00:00
|
|
|
|
2021-11-06 13:23:58 +00:00
|
|
|
// Delete older messages
|
|
|
|
if d.maxDuration > 0 {
|
2022-05-19 21:29:15 +00:00
|
|
|
start := time.Now()
|
2024-01-03 16:49:54 +00:00
|
|
|
sqlStmt := `DELETE FROM message WHERE storedAt < $1`
|
2023-11-07 19:48:43 +00:00
|
|
|
_, err := d.db.Exec(sqlStmt, d.timesource.Now().Add(-d.maxDuration).UnixNano())
|
2021-11-05 14:27:30 +00:00
|
|
|
if err != nil {
|
2023-08-16 01:40:00 +00:00
|
|
|
d.metrics.RecordError(retPolicyFailure)
|
2021-11-05 14:27:30 +00:00
|
|
|
return err
|
|
|
|
}
|
2022-05-19 21:29:15 +00:00
|
|
|
elapsed := time.Since(start)
|
2022-05-30 15:55:30 +00:00
|
|
|
d.log.Debug("deleting older records from the DB", zap.Duration("duration", elapsed))
|
2021-11-05 14:27:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Limit number of records to a max N
|
|
|
|
if d.maxMessages > 0 {
|
2022-05-19 21:29:15 +00:00
|
|
|
start := time.Now()
|
2023-10-03 16:02:23 +00:00
|
|
|
|
|
|
|
_, err := d.db.Exec(d.getDeleteOldRowsQuery(), d.maxMessages)
|
2021-11-05 14:27:30 +00:00
|
|
|
if err != nil {
|
2023-08-16 01:40:00 +00:00
|
|
|
d.metrics.RecordError(retPolicyFailure)
|
2021-11-05 14:27:30 +00:00
|
|
|
return err
|
|
|
|
}
|
2022-05-19 21:29:15 +00:00
|
|
|
elapsed := time.Since(start)
|
2022-05-30 15:55:30 +00:00
|
|
|
d.log.Debug("deleting excess records from the DB", zap.Duration("duration", elapsed))
|
2022-05-05 18:11:23 +00:00
|
|
|
}
|
|
|
|
|
2022-11-25 20:54:11 +00:00
|
|
|
d.log.Info("Older records removed")
|
|
|
|
|
2021-11-05 14:27:30 +00:00
|
|
|
return nil
|
|
|
|
}
|
2023-10-05 00:20:02 +00:00
|
|
|
|
2023-10-03 16:02:23 +00:00
|
|
|
func (d *DBStore) getDeleteOldRowsQuery() string {
|
2024-01-03 16:49:54 +00:00
|
|
|
sqlStmt := `DELETE FROM message WHERE id IN (SELECT id FROM message ORDER BY storedAt DESC %s OFFSET $1)`
|
2023-10-05 00:20:02 +00:00
|
|
|
switch GetDriverType(d.db) {
|
|
|
|
case SQLiteDriver:
|
2023-10-03 16:02:23 +00:00
|
|
|
sqlStmt = fmt.Sprintf(sqlStmt, "LIMIT -1")
|
2023-10-05 00:20:02 +00:00
|
|
|
case PostgresDriver:
|
2023-10-03 16:02:23 +00:00
|
|
|
sqlStmt = fmt.Sprintf(sqlStmt, "")
|
|
|
|
}
|
|
|
|
return sqlStmt
|
|
|
|
}
|
2021-11-05 14:27:30 +00:00
|
|
|
|
2023-03-09 15:48:25 +00:00
|
|
|
func (d *DBStore) checkForOlderRecords(ctx context.Context, t time.Duration) {
|
2024-09-25 09:15:20 +00:00
|
|
|
defer utils.LogOnPanic()
|
2022-05-30 18:48:22 +00:00
|
|
|
defer d.wg.Done()
|
|
|
|
|
|
|
|
ticker := time.NewTicker(t)
|
|
|
|
defer ticker.Stop()
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
2023-03-09 15:48:25 +00:00
|
|
|
case <-ctx.Done():
|
2022-05-30 18:48:22 +00:00
|
|
|
return
|
|
|
|
case <-ticker.C:
|
2023-04-19 20:54:33 +00:00
|
|
|
err := d.cleanOlderRecords(ctx)
|
2022-05-27 19:55:35 +00:00
|
|
|
if err != nil {
|
|
|
|
d.log.Error("cleaning older records", zap.Error(err))
|
|
|
|
}
|
2022-05-30 18:48:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-25 15:28:17 +00:00
|
|
|
// Stop closes a DB connection
|
2021-04-12 17:59:41 +00:00
|
|
|
func (d *DBStore) Stop() {
|
2023-03-09 15:48:25 +00:00
|
|
|
if d.cancel == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
d.cancel()
|
2022-05-30 18:48:22 +00:00
|
|
|
d.wg.Wait()
|
2021-04-12 17:59:41 +00:00
|
|
|
d.db.Close()
|
|
|
|
}
|
|
|
|
|
2023-07-07 01:38:23 +00:00
|
|
|
// Validate validates the message to be stored against possible fradulent conditions.
|
2023-04-19 20:54:33 +00:00
|
|
|
func (d *DBStore) Validate(env *protocol.Envelope) error {
|
2024-01-03 16:49:54 +00:00
|
|
|
timestamp := env.Message().GetTimestamp()
|
|
|
|
if timestamp == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
n := time.Unix(0, timestamp)
|
2023-04-19 20:54:33 +00:00
|
|
|
upperBound := n.Add(MaxTimeVariance)
|
|
|
|
lowerBound := n.Add(-MaxTimeVariance)
|
|
|
|
|
|
|
|
// Ensure that messages don't "jump" to the front of the queue with future timestamps
|
2023-11-07 19:48:43 +00:00
|
|
|
if env.Message().GetTimestamp() > upperBound.UnixNano() {
|
2023-04-19 20:54:33 +00:00
|
|
|
return ErrFutureMessage
|
|
|
|
}
|
|
|
|
|
2023-11-07 19:48:43 +00:00
|
|
|
if env.Message().GetTimestamp() < lowerBound.UnixNano() {
|
2023-04-19 20:54:33 +00:00
|
|
|
return ErrMessageTooOld
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-07-25 15:28:17 +00:00
|
|
|
// Put inserts a WakuMessage into the DB
|
2022-05-30 18:48:22 +00:00
|
|
|
func (d *DBStore) Put(env *protocol.Envelope) error {
|
2024-01-03 16:49:54 +00:00
|
|
|
|
|
|
|
stmt, err := d.db.Prepare("INSERT INTO message (id, messageHash, storedAt, timestamp, contentTopic, pubsubTopic, payload, version) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)")
|
2021-04-12 17:59:41 +00:00
|
|
|
if err != nil {
|
2023-08-16 01:40:00 +00:00
|
|
|
d.metrics.RecordError(insertFailure)
|
2021-04-12 17:59:41 +00:00
|
|
|
return err
|
|
|
|
}
|
2022-05-30 18:48:22 +00:00
|
|
|
|
2024-01-03 16:49:54 +00:00
|
|
|
storedAt := env.Message().GetTimestamp()
|
|
|
|
if storedAt == 0 {
|
|
|
|
storedAt = env.Index().ReceiverTime
|
|
|
|
}
|
2023-04-19 20:54:33 +00:00
|
|
|
|
2024-05-03 16:07:03 +00:00
|
|
|
hash := env.Hash()
|
|
|
|
|
2023-04-19 20:54:33 +00:00
|
|
|
start := time.Now()
|
2024-05-03 16:07:03 +00:00
|
|
|
_, err = stmt.Exec(env.Index().Digest, hash[:], storedAt, env.Message().GetTimestamp(), env.Message().ContentTopic, env.PubsubTopic(), env.Message().Payload, env.Message().GetVersion())
|
2021-04-12 17:59:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-08-16 01:40:00 +00:00
|
|
|
|
|
|
|
d.metrics.RecordInsertDuration(time.Since(start))
|
2021-04-12 17:59:41 +00:00
|
|
|
|
2022-05-27 18:34:13 +00:00
|
|
|
err = stmt.Close()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-04-12 17:59:41 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-07-07 01:38:23 +00:00
|
|
|
func (d *DBStore) handleQueryCursor(query *pb.HistoryQuery, paramCnt *int, conditions []string, parameters []interface{}) ([]string, []interface{}, error) {
|
2022-11-25 20:54:11 +00:00
|
|
|
usesCursor := false
|
2022-05-30 18:48:22 +00:00
|
|
|
if query.PagingInfo.Cursor != nil {
|
2022-11-25 20:54:11 +00:00
|
|
|
usesCursor = true
|
2022-05-30 18:48:22 +00:00
|
|
|
|
2024-01-03 16:49:54 +00:00
|
|
|
var exists bool
|
|
|
|
err := d.db.QueryRow("SELECT EXISTS(SELECT 1 FROM message WHERE storedAt = $1 AND id = $2)",
|
|
|
|
query.PagingInfo.Cursor.ReceiverTime, query.PagingInfo.Cursor.Digest,
|
2022-05-30 18:48:22 +00:00
|
|
|
).Scan(&exists)
|
|
|
|
if err != nil {
|
2022-09-15 13:23:45 +00:00
|
|
|
return nil, nil, err
|
2022-05-30 18:48:22 +00:00
|
|
|
}
|
|
|
|
|
2024-01-03 16:49:54 +00:00
|
|
|
if !exists {
|
2022-09-15 13:23:45 +00:00
|
|
|
return nil, nil, ErrInvalidCursor
|
2022-05-30 18:48:22 +00:00
|
|
|
}
|
2024-01-03 16:49:54 +00:00
|
|
|
|
|
|
|
eqOp := ">"
|
|
|
|
if query.PagingInfo.Direction == pb.PagingInfo_BACKWARD {
|
|
|
|
eqOp = "<"
|
|
|
|
}
|
|
|
|
conditions = append(conditions, fmt.Sprintf("(storedAt, id) %s ($%d, $%d)", eqOp, *paramCnt+1, *paramCnt+2))
|
|
|
|
*paramCnt += 2
|
|
|
|
|
|
|
|
parameters = append(parameters, query.PagingInfo.Cursor.ReceiverTime, query.PagingInfo.Cursor.Digest)
|
2022-05-30 18:48:22 +00:00
|
|
|
}
|
|
|
|
|
2023-07-07 01:38:23 +00:00
|
|
|
handleTimeParam := func(time int64, op string) {
|
|
|
|
*paramCnt++
|
2024-01-03 16:49:54 +00:00
|
|
|
conditions = append(conditions, fmt.Sprintf("storedAt %s $%d", op, *paramCnt))
|
|
|
|
parameters = append(parameters, time)
|
2023-07-07 01:38:23 +00:00
|
|
|
}
|
|
|
|
|
2023-11-07 19:48:43 +00:00
|
|
|
startTime := query.GetStartTime()
|
|
|
|
if startTime != 0 {
|
2022-11-25 20:54:11 +00:00
|
|
|
if !usesCursor || query.PagingInfo.Direction == pb.PagingInfo_BACKWARD {
|
2023-11-07 19:48:43 +00:00
|
|
|
handleTimeParam(startTime, ">=")
|
2022-11-25 20:54:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-07 19:48:43 +00:00
|
|
|
endTime := query.GetEndTime()
|
|
|
|
if endTime != 0 {
|
2022-11-25 20:54:11 +00:00
|
|
|
if !usesCursor || query.PagingInfo.Direction == pb.PagingInfo_FORWARD {
|
2023-11-07 19:48:43 +00:00
|
|
|
handleTimeParam(endTime+1, "<")
|
2023-07-07 01:38:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return conditions, parameters, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *DBStore) prepareQuerySQL(query *pb.HistoryQuery) (string, []interface{}, error) {
|
2024-01-03 16:49:54 +00:00
|
|
|
sqlQuery := `SELECT id, storedAt, timestamp, contentTopic, pubsubTopic, payload, version
|
2023-07-07 01:38:23 +00:00
|
|
|
FROM message
|
|
|
|
%s
|
2024-01-03 16:49:54 +00:00
|
|
|
ORDER BY timestamp %s, id %s, pubsubTopic %s, storedAt %s `
|
2023-07-07 01:38:23 +00:00
|
|
|
|
|
|
|
var conditions []string
|
|
|
|
//var parameters []interface{}
|
|
|
|
parameters := make([]interface{}, 0) //Allocating as a slice so that references get passed rather than value
|
|
|
|
paramCnt := 0
|
|
|
|
|
|
|
|
if query.PubsubTopic != "" {
|
|
|
|
paramCnt++
|
|
|
|
conditions = append(conditions, fmt.Sprintf("pubsubTopic = $%d", paramCnt))
|
|
|
|
parameters = append(parameters, query.PubsubTopic)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(query.ContentFilters) != 0 {
|
|
|
|
var ctPlaceHolder []string
|
|
|
|
for _, ct := range query.ContentFilters {
|
|
|
|
if ct.ContentTopic != "" {
|
|
|
|
paramCnt++
|
|
|
|
ctPlaceHolder = append(ctPlaceHolder, fmt.Sprintf("$%d", paramCnt))
|
|
|
|
parameters = append(parameters, ct.ContentTopic)
|
|
|
|
}
|
2022-11-25 20:54:11 +00:00
|
|
|
}
|
2023-07-07 01:38:23 +00:00
|
|
|
conditions = append(conditions, "contentTopic IN ("+strings.Join(ctPlaceHolder, ", ")+")")
|
2022-11-25 20:54:11 +00:00
|
|
|
}
|
|
|
|
|
2023-07-07 01:38:23 +00:00
|
|
|
conditions, parameters, err := d.handleQueryCursor(query, ¶mCnt, conditions, parameters)
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
2022-05-30 18:48:22 +00:00
|
|
|
conditionStr := ""
|
|
|
|
if len(conditions) != 0 {
|
|
|
|
conditionStr = "WHERE " + strings.Join(conditions, " AND ")
|
|
|
|
}
|
|
|
|
|
|
|
|
orderDirection := "ASC"
|
|
|
|
if query.PagingInfo.Direction == pb.PagingInfo_BACKWARD {
|
|
|
|
orderDirection = "DESC"
|
|
|
|
}
|
|
|
|
|
2023-01-04 17:58:14 +00:00
|
|
|
paramCnt++
|
2023-07-07 01:38:23 +00:00
|
|
|
|
2023-01-04 17:58:14 +00:00
|
|
|
sqlQuery += fmt.Sprintf("LIMIT $%d", paramCnt)
|
2023-09-19 06:28:11 +00:00
|
|
|
// Always search for _max page size_ + 1. If the extra row does not exist, do not return pagination info.
|
|
|
|
pageSize := query.PagingInfo.PageSize + 1
|
|
|
|
parameters = append(parameters, pageSize)
|
|
|
|
|
2022-09-21 13:22:22 +00:00
|
|
|
sqlQuery = fmt.Sprintf(sqlQuery, conditionStr, orderDirection, orderDirection, orderDirection, orderDirection)
|
2024-01-03 16:49:54 +00:00
|
|
|
d.log.Debug(fmt.Sprintf("sqlQuery: %s", sqlQuery))
|
2023-07-07 01:38:23 +00:00
|
|
|
|
|
|
|
return sqlQuery, parameters, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Query retrieves messages from the DB
|
|
|
|
func (d *DBStore) Query(query *pb.HistoryQuery) (*pb.Index, []StoredMessage, error) {
|
|
|
|
start := time.Now()
|
|
|
|
defer func() {
|
|
|
|
elapsed := time.Since(start)
|
|
|
|
d.log.Info(fmt.Sprintf("Loading records from the DB took %s", elapsed))
|
|
|
|
}()
|
2022-05-30 18:48:22 +00:00
|
|
|
|
2023-07-07 01:38:23 +00:00
|
|
|
sqlQuery, parameters, err := d.prepareQuerySQL(query)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2022-05-30 18:48:22 +00:00
|
|
|
stmt, err := d.db.Prepare(sqlQuery)
|
|
|
|
if err != nil {
|
2022-09-15 13:23:45 +00:00
|
|
|
return nil, nil, err
|
2022-05-30 18:48:22 +00:00
|
|
|
}
|
|
|
|
defer stmt.Close()
|
2023-09-19 06:28:11 +00:00
|
|
|
//
|
2023-04-19 20:54:33 +00:00
|
|
|
measurementStart := time.Now()
|
2022-05-30 18:48:22 +00:00
|
|
|
rows, err := stmt.Query(parameters...)
|
|
|
|
if err != nil {
|
2022-09-15 13:23:45 +00:00
|
|
|
return nil, nil, err
|
2022-05-30 18:48:22 +00:00
|
|
|
}
|
2023-08-16 01:40:00 +00:00
|
|
|
|
|
|
|
d.metrics.RecordQueryDuration(time.Since(measurementStart))
|
2022-05-30 18:48:22 +00:00
|
|
|
|
|
|
|
var result []StoredMessage
|
|
|
|
for rows.Next() {
|
|
|
|
record, err := d.GetStoredMessage(rows)
|
|
|
|
if err != nil {
|
2022-09-15 13:23:45 +00:00
|
|
|
return nil, nil, err
|
2022-05-30 18:48:22 +00:00
|
|
|
}
|
|
|
|
result = append(result, record)
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
2022-11-25 20:54:11 +00:00
|
|
|
var cursor *pb.Index
|
2022-09-15 13:23:45 +00:00
|
|
|
if len(result) != 0 {
|
2023-09-19 06:28:11 +00:00
|
|
|
// since there are more rows than pagingInfo.PageSize, we need to return a cursor, for pagination
|
2022-10-03 19:26:45 +00:00
|
|
|
if len(result) > int(query.PagingInfo.PageSize) {
|
|
|
|
result = result[0:query.PagingInfo.PageSize]
|
|
|
|
lastMsgIdx := len(result) - 1
|
|
|
|
cursor = protocol.NewEnvelope(result[lastMsgIdx].Message, result[lastMsgIdx].ReceiverTime, result[lastMsgIdx].PubsubTopic).Index()
|
|
|
|
}
|
2022-09-15 13:23:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// The retrieved messages list should always be in chronological order
|
|
|
|
if query.PagingInfo.Direction == pb.PagingInfo_BACKWARD {
|
|
|
|
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
|
|
|
|
result[i], result[j] = result[j], result[i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cursor, result, nil
|
2022-05-30 18:48:22 +00:00
|
|
|
}
|
|
|
|
|
2024-01-03 16:49:54 +00:00
|
|
|
// MostRecentTimestamp returns an unix timestamp with the most recent timestamp
|
2022-07-25 15:28:17 +00:00
|
|
|
// in the message table
|
2022-05-30 18:48:22 +00:00
|
|
|
func (d *DBStore) MostRecentTimestamp() (int64, error) {
|
|
|
|
result := sql.NullInt64{}
|
|
|
|
|
2024-01-03 16:49:54 +00:00
|
|
|
err := d.db.QueryRow(`SELECT max(timestamp) FROM message`).Scan(&result)
|
2022-05-30 18:48:22 +00:00
|
|
|
if err != nil && err != sql.ErrNoRows {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return result.Int64, nil
|
|
|
|
}
|
|
|
|
|
2022-07-28 19:17:12 +00:00
|
|
|
// Count returns the number of rows in the message table
|
|
|
|
func (d *DBStore) Count() (int, error) {
|
|
|
|
var result int
|
|
|
|
err := d.db.QueryRow(`SELECT COUNT(*) FROM message`).Scan(&result)
|
|
|
|
if err != nil && err != sql.ErrNoRows {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2022-07-25 15:28:17 +00:00
|
|
|
// GetAll returns all the stored WakuMessages
|
2021-10-25 19:41:08 +00:00
|
|
|
func (d *DBStore) GetAll() ([]StoredMessage, error) {
|
2022-05-19 20:30:41 +00:00
|
|
|
start := time.Now()
|
|
|
|
defer func() {
|
|
|
|
elapsed := time.Since(start)
|
2022-05-30 15:55:30 +00:00
|
|
|
d.log.Info("loading records from the DB", zap.Duration("duration", elapsed))
|
2022-05-19 20:30:41 +00:00
|
|
|
}()
|
|
|
|
|
2024-01-03 16:49:54 +00:00
|
|
|
rows, err := d.db.Query("SELECT id, storedAt, timestamp, contentTopic, pubsubTopic, payload, version FROM message ORDER BY timestamp ASC")
|
2021-04-12 17:59:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-10-25 19:41:08 +00:00
|
|
|
var result []StoredMessage
|
2021-04-12 17:59:41 +00:00
|
|
|
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
for rows.Next() {
|
2022-05-30 18:48:22 +00:00
|
|
|
record, err := d.GetStoredMessage(rows)
|
2021-04-12 17:59:41 +00:00
|
|
|
if err != nil {
|
2022-05-30 18:48:22 +00:00
|
|
|
return nil, err
|
2021-07-11 18:11:38 +00:00
|
|
|
}
|
|
|
|
result = append(result, record)
|
2021-04-12 17:59:41 +00:00
|
|
|
}
|
|
|
|
|
2022-05-30 15:55:30 +00:00
|
|
|
d.log.Info("DB returned records", zap.Int("count", len(result)))
|
2022-05-19 20:30:41 +00:00
|
|
|
|
2021-04-12 17:59:41 +00:00
|
|
|
err = rows.Err()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
2022-05-30 18:48:22 +00:00
|
|
|
|
2022-07-25 15:28:17 +00:00
|
|
|
// GetStoredMessage is a helper function used to convert a `*sql.Rows` into a `StoredMessage`
|
|
|
|
func (d *DBStore) GetStoredMessage(row *sql.Rows) (StoredMessage, error) {
|
2022-05-30 18:48:22 +00:00
|
|
|
var id []byte
|
2024-01-03 16:49:54 +00:00
|
|
|
var storedAt int64
|
|
|
|
var timestamp int64
|
2022-05-30 18:48:22 +00:00
|
|
|
var contentTopic string
|
|
|
|
var payload []byte
|
|
|
|
var version uint32
|
|
|
|
var pubsubTopic string
|
|
|
|
|
2024-01-03 16:49:54 +00:00
|
|
|
err := row.Scan(&id, &storedAt, ×tamp, &contentTopic, &pubsubTopic, &payload, &version)
|
2022-05-30 18:48:22 +00:00
|
|
|
if err != nil {
|
|
|
|
d.log.Error("scanning messages from db", zap.Error(err))
|
|
|
|
return StoredMessage{}, err
|
|
|
|
}
|
|
|
|
|
2023-02-06 22:16:20 +00:00
|
|
|
msg := new(wpb.WakuMessage)
|
2022-05-30 18:48:22 +00:00
|
|
|
msg.ContentTopic = contentTopic
|
|
|
|
msg.Payload = payload
|
2023-11-07 19:48:43 +00:00
|
|
|
|
2024-01-03 16:49:54 +00:00
|
|
|
if timestamp != 0 {
|
|
|
|
msg.Timestamp = proto.Int64(timestamp)
|
2023-11-07 19:48:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if version > 0 {
|
|
|
|
msg.Version = proto.Uint32(version)
|
|
|
|
}
|
2022-05-30 18:48:22 +00:00
|
|
|
|
|
|
|
record := StoredMessage{
|
|
|
|
ID: id,
|
|
|
|
PubsubTopic: pubsubTopic,
|
2024-01-03 16:49:54 +00:00
|
|
|
ReceiverTime: storedAt,
|
2022-05-30 18:48:22 +00:00
|
|
|
Message: msg,
|
|
|
|
}
|
|
|
|
|
|
|
|
return record, nil
|
|
|
|
}
|