2021-04-13 18:52:57 +00:00
package persistence
2021-04-12 17:59:41 +00:00
import (
"database/sql"
2021-11-05 14:27:30 +00:00
"time"
2021-04-12 17:59:41 +00:00
2021-04-22 00:09:37 +00:00
"github.com/status-im/go-waku/waku/v2/protocol/pb"
2021-11-05 14:27:30 +00:00
"github.com/status-im/go-waku/waku/v2/utils"
2022-01-18 18:17:06 +00:00
"go.uber.org/zap"
2021-04-12 17:59:41 +00:00
)
2021-10-25 19:41:08 +00:00
type MessageProvider interface {
GetAll ( ) ( [ ] StoredMessage , error )
Put ( cursor * pb . Index , pubsubTopic string , message * pb . WakuMessage ) error
Stop ( )
}
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
2022-01-18 18:17:06 +00:00
db * sql . DB
log * zap . SugaredLogger
2021-11-05 14:27:30 +00:00
maxMessages int
2021-11-06 13:23:58 +00:00
maxDuration time . Duration
2021-04-12 17:59:41 +00:00
}
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
2021-10-25 19:41:08 +00:00
Message * pb . WakuMessage
}
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
}
}
2021-04-22 18:49:52 +00:00
// WithDriver is a DBOption that will open a *sql.DB connection
2021-04-13 18:52:57 +00:00
func WithDriver ( driverName string , datasourceName string ) DBOption {
return func ( d * DBStore ) error {
db , err := sql . Open ( driverName , datasourceName )
if err != nil {
return err
}
d . db = db
return nil
}
}
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
}
}
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
2022-01-18 18:17:06 +00:00
func NewDBStore ( log * zap . SugaredLogger , 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" )
2021-04-13 18:52:57 +00:00
2021-11-05 14:27:30 +00:00
for _ , opt := range options {
err := opt ( result )
if err != nil {
return nil , err
}
}
err := result . createTable ( )
2021-04-12 17:59:41 +00:00
if err != nil {
return nil , err
}
2021-11-05 14:27:30 +00:00
err = result . cleanOlderRecords ( )
2021-04-12 17:59:41 +00:00
if err != nil {
return nil , err
}
return result , nil
}
func ( d * DBStore ) createTable ( ) error {
2021-04-28 15:11:32 +00:00
sqlStmt := ` CREATE TABLE IF NOT EXISTS message (
2022-03-02 16:02:13 +00:00
id BLOB ,
2022-02-23 15:01:53 +00:00
receiverTimestamp INTEGER NOT NULL ,
senderTimestamp INTEGER NOT NULL ,
2021-04-12 17:59:41 +00:00
contentTopic BLOB NOT NULL ,
2021-04-28 15:11:32 +00:00
pubsubTopic BLOB NOT NULL ,
2021-04-12 17:59:41 +00:00
payload BLOB ,
2022-03-02 16:02:13 +00:00
version INTEGER NOT NULL DEFAULT 0 ,
CONSTRAINT messageIndex PRIMARY KEY ( senderTimestamp , id , pubsubTopic )
2021-04-12 17:59:41 +00:00
) WITHOUT ROWID ; `
_ , err := d . db . Exec ( sqlStmt )
if err != nil {
return err
}
return nil
}
2021-11-05 14:27:30 +00:00
func ( d * DBStore ) cleanOlderRecords ( ) error {
2021-11-06 13:23:58 +00:00
// Delete older messages
if d . maxDuration > 0 {
2021-11-05 14:27:30 +00:00
sqlStmt := ` DELETE FROM message WHERE receiverTimestamp < ? `
2021-11-06 13:50:38 +00:00
_ , err := d . db . Exec ( sqlStmt , utils . GetUnixEpochFrom ( time . Now ( ) . Add ( - d . maxDuration ) ) )
2021-11-05 14:27:30 +00:00
if err != nil {
return err
}
}
// Limit number of records to a max N
if d . maxMessages > 0 {
sqlStmt := ` DELETE FROM message WHERE id IN (SELECT id FROM message ORDER BY receiverTimestamp DESC LIMIT -1 OFFSET 5) `
_ , err := d . db . Exec ( sqlStmt , d . maxMessages )
if err != nil {
return err
}
}
2022-05-05 18:11:23 +00:00
// reduce the size of the DB file after the delete operation. See: https://www.sqlite.org/lang_vacuum.html
_ , err := d . db . Exec ( "VACUUM" )
if err != nil {
return err
}
2021-11-05 14:27:30 +00:00
return nil
}
2021-04-22 18:49:52 +00:00
// Closes a DB connection
2021-04-12 17:59:41 +00:00
func ( d * DBStore ) Stop ( ) {
d . db . Close ( )
}
2021-04-22 18:49:52 +00:00
// Inserts a WakuMessage into the DB
2021-04-28 15:11:32 +00:00
func ( d * DBStore ) Put ( cursor * pb . Index , pubsubTopic string , message * pb . WakuMessage ) error {
2021-07-29 15:03:30 +00:00
stmt , err := d . db . Prepare ( "INSERT INTO message (id, receiverTimestamp, senderTimestamp, contentTopic, pubsubTopic, payload, version) VALUES (?, ?, ?, ?, ?, ?, ?)" )
2021-04-12 17:59:41 +00:00
if err != nil {
return err
}
2021-07-29 15:03:30 +00:00
_ , err = stmt . Exec ( cursor . Digest , cursor . ReceiverTime , message . Timestamp , message . ContentTopic , pubsubTopic , message . Payload , message . Version )
2021-04-12 17:59:41 +00:00
if err != nil {
return err
}
return nil
}
2021-04-22 18:49:52 +00:00
// Returns all the stored WakuMessages
2021-10-25 19:41:08 +00:00
func ( d * DBStore ) GetAll ( ) ( [ ] StoredMessage , error ) {
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 ( ) {
2021-07-11 18:11:38 +00:00
var id [ ] byte
2022-02-23 15:01:53 +00:00
var receiverTimestamp int64
var senderTimestamp int64
2021-04-12 17:59:41 +00:00
var contentTopic string
var payload [ ] byte
var version uint32
2021-04-28 15:11:32 +00:00
var pubsubTopic string
2021-04-12 17:59:41 +00:00
2021-07-11 18:11:38 +00:00
err = rows . Scan ( & id , & receiverTimestamp , & senderTimestamp , & contentTopic , & pubsubTopic , & payload , & version )
2021-04-12 17:59:41 +00:00
if err != nil {
2022-01-18 18:17:06 +00:00
d . log . Fatal ( err )
2021-04-12 17:59:41 +00:00
}
2021-04-22 00:09:37 +00:00
msg := new ( pb . WakuMessage )
2021-04-12 17:59:41 +00:00
msg . ContentTopic = contentTopic
msg . Payload = payload
2021-07-29 15:03:30 +00:00
msg . Timestamp = senderTimestamp
2021-04-12 17:59:41 +00:00
msg . Version = version
2021-10-25 19:41:08 +00:00
record := StoredMessage {
2021-07-11 18:11:38 +00:00
ID : id ,
PubsubTopic : pubsubTopic ,
ReceiverTime : receiverTimestamp ,
Message : msg ,
}
result = append ( result , record )
2021-04-12 17:59:41 +00:00
}
err = rows . Err ( )
if err != nil {
return nil , err
}
return result , nil
}