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"
2023-02-06 22:16:20 +00:00
wpb "github.com/waku-org/go-waku/waku/v2/protocol/pb"
"github.com/waku-org/go-waku/waku/v2/protocol/store/pb"
2022-12-09 03:08:04 +00:00
"github.com/waku-org/go-waku/waku/v2/timesource"
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
migrationFn func ( db * sql . DB ) error
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
}
}
2023-08-09 17:23:44 +00:00
type MigrationFn func ( db * sql . DB ) error
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 {
2023-01-04 17:58:14 +00:00
err := result . migrationFn ( result . db )
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 ) {
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 ( )
2023-01-04 17:58:14 +00:00
sqlStmt := ` DELETE FROM message WHERE receiverTimestamp < $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 {
sqlStmt := ` DELETE FROM message WHERE id IN (SELECT id FROM message ORDER BY receiverTimestamp 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 ) {
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 {
n := time . Unix ( 0 , env . Index ( ) . ReceiverTime )
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 {
2023-01-04 17:58:14 +00:00
stmt , err := d . db . Prepare ( "INSERT INTO message (id, receiverTimestamp, senderTimestamp, contentTopic, pubsubTopic, payload, version) VALUES ($1, $2, $3, $4, $5, $6, $7)" )
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
cursor := env . Index ( )
2022-12-16 00:55:13 +00:00
dbKey := NewDBKey ( uint64 ( cursor . SenderTime ) , uint64 ( cursor . ReceiverTime ) , env . PubsubTopic ( ) , env . Index ( ) . Digest )
2023-04-19 20:54:33 +00:00
start := time . Now ( )
2023-11-07 19:48:43 +00:00
_ , err = stmt . Exec ( dbKey . Bytes ( ) , cursor . ReceiverTime , 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
var exists bool
2022-12-16 00:55:13 +00:00
cursorDBKey := NewDBKey ( uint64 ( query . PagingInfo . Cursor . SenderTime ) , uint64 ( query . PagingInfo . Cursor . ReceiverTime ) , query . PagingInfo . Cursor . PubsubTopic , query . PagingInfo . Cursor . Digest )
2022-05-30 18:48:22 +00:00
2023-01-04 17:58:14 +00:00
err := d . db . QueryRow ( "SELECT EXISTS(SELECT 1 FROM message WHERE id = $1)" ,
2022-05-30 18:48:22 +00:00
cursorDBKey . Bytes ( ) ,
) . 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
}
if exists {
eqOp := ">"
if query . PagingInfo . Direction == pb . PagingInfo_BACKWARD {
eqOp = "<"
}
2023-07-07 01:38:23 +00:00
* paramCnt ++
conditions = append ( conditions , fmt . Sprintf ( "id %s $%d" , eqOp , * paramCnt ) )
2022-05-30 18:48:22 +00:00
parameters = append ( parameters , cursorDBKey . Bytes ( ) )
} else {
2022-09-15 13:23:45 +00:00
return nil , nil , ErrInvalidCursor
2022-05-30 18:48:22 +00:00
}
}
2023-07-07 01:38:23 +00:00
handleTimeParam := func ( time int64 , op string ) {
* paramCnt ++
conditions = append ( conditions , fmt . Sprintf ( "id %s $%d" , op , * paramCnt ) )
2023-10-12 12:42:53 +00:00
timeDBKey := NewDBKey ( uint64 ( time ) , 0 , "" , [ ] byte { } )
2023-07-07 01:38:23 +00:00
parameters = append ( parameters , timeDBKey . Bytes ( ) )
}
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 ) {
sqlQuery := ` SELECT id , receiverTimestamp , senderTimestamp , contentTopic , pubsubTopic , payload , version
FROM message
% s
ORDER BY senderTimestamp % s , id % s , pubsubTopic % s , receiverTimestamp % s `
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 , & paramCnt , 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 )
2023-07-07 01:38:23 +00:00
d . log . Info ( fmt . Sprintf ( "sqlQuery: %s" , sqlQuery ) )
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
}
2022-07-25 15:28:17 +00:00
// MostRecentTimestamp returns an unix timestamp with the most recent senderTimestamp
// in the message table
2022-05-30 18:48:22 +00:00
func ( d * DBStore ) MostRecentTimestamp ( ) ( int64 , error ) {
result := sql . NullInt64 { }
err := d . db . QueryRow ( ` SELECT max(senderTimestamp) FROM message ` ) . Scan ( & result )
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
} ( )
2021-07-29 15:03:30 +00:00
rows , err := d . db . Query ( "SELECT id, receiverTimestamp, senderTimestamp, contentTopic, pubsubTopic, payload, version FROM message ORDER BY senderTimestamp 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
var receiverTimestamp int64
var senderTimestamp int64
var contentTopic string
var payload [ ] byte
var version uint32
var pubsubTopic string
2022-07-25 15:28:17 +00:00
err := row . Scan ( & id , & receiverTimestamp , & senderTimestamp , & 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
if senderTimestamp != 0 {
msg . Timestamp = proto . Int64 ( senderTimestamp )
}
if version > 0 {
msg . Version = proto . Uint32 ( version )
}
2022-05-30 18:48:22 +00:00
record := StoredMessage {
ID : id ,
PubsubTopic : pubsubTopic ,
ReceiverTime : receiverTimestamp ,
Message : msg ,
}
return record , nil
}