2018-08-25 12:48:03 +02:00
|
|
|
package influxdb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
2018-11-05 19:18:51 +01:00
|
|
|
"strings"
|
2018-11-18 16:42:39 +01:00
|
|
|
"time"
|
2018-08-25 12:48:03 +02:00
|
|
|
|
2020-06-03 15:16:19 +02:00
|
|
|
"github.com/mainflux/mainflux/pkg/errors"
|
2018-08-25 12:48:03 +02:00
|
|
|
"github.com/mainflux/mainflux/readers"
|
|
|
|
|
|
|
|
influxdata "github.com/influxdata/influxdb/client/v2"
|
2020-06-03 15:16:19 +02:00
|
|
|
"github.com/mainflux/mainflux/pkg/transformers/senml"
|
2018-08-25 12:48:03 +02:00
|
|
|
)
|
|
|
|
|
2019-11-26 18:58:45 +01:00
|
|
|
const countCol = "count"
|
2018-08-25 12:48:03 +02:00
|
|
|
|
2020-04-13 12:57:53 +02:00
|
|
|
var errReadMessages = errors.New("faled to read messages from influxdb database")
|
|
|
|
|
2018-08-25 12:48:03 +02:00
|
|
|
var _ readers.MessageRepository = (*influxRepository)(nil)
|
|
|
|
|
|
|
|
type influxRepository struct {
|
|
|
|
database string
|
|
|
|
client influxdata.Client
|
|
|
|
}
|
|
|
|
|
|
|
|
// New returns new InfluxDB reader.
|
2019-05-07 15:59:18 +02:00
|
|
|
func New(client influxdata.Client, database string) readers.MessageRepository {
|
|
|
|
return &influxRepository{
|
|
|
|
database,
|
|
|
|
client,
|
|
|
|
}
|
2018-08-25 12:48:03 +02:00
|
|
|
}
|
|
|
|
|
2019-05-07 15:59:18 +02:00
|
|
|
func (repo *influxRepository) ReadAll(chanID string, offset, limit uint64, query map[string]string) (readers.MessagesPage, error) {
|
2019-03-15 18:38:07 +01:00
|
|
|
condition := fmtCondition(chanID, query)
|
2019-04-25 00:18:43 +02:00
|
|
|
cmd := fmt.Sprintf(`SELECT * FROM messages WHERE %s ORDER BY time DESC LIMIT %d OFFSET %d`, condition, limit, offset)
|
2018-08-25 12:48:03 +02:00
|
|
|
q := influxdata.Query{
|
|
|
|
Command: cmd,
|
|
|
|
Database: repo.database,
|
|
|
|
}
|
|
|
|
|
2019-11-05 11:57:16 +01:00
|
|
|
ret := []senml.Message{}
|
2018-08-25 12:48:03 +02:00
|
|
|
|
|
|
|
resp, err := repo.client.Query(q)
|
2019-05-07 15:59:18 +02:00
|
|
|
if err != nil {
|
2020-04-13 12:57:53 +02:00
|
|
|
return readers.MessagesPage{}, errors.Wrap(errReadMessages, err)
|
2019-05-07 15:59:18 +02:00
|
|
|
}
|
|
|
|
if resp.Error() != nil {
|
2020-04-13 12:57:53 +02:00
|
|
|
return readers.MessagesPage{}, errors.Wrap(errReadMessages, resp.Error())
|
2018-08-25 12:48:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Results) < 1 || len(resp.Results[0].Series) < 1 {
|
2019-05-07 15:59:18 +02:00
|
|
|
return readers.MessagesPage{}, nil
|
2018-08-25 12:48:03 +02:00
|
|
|
}
|
2018-11-18 16:42:39 +01:00
|
|
|
|
2018-08-25 12:48:03 +02:00
|
|
|
result := resp.Results[0].Series[0]
|
|
|
|
for _, v := range result.Values {
|
2018-11-05 19:18:51 +01:00
|
|
|
ret = append(ret, parseMessage(result.Columns, v))
|
2018-08-25 12:48:03 +02:00
|
|
|
}
|
|
|
|
|
2019-04-25 00:18:43 +02:00
|
|
|
total, err := repo.count(condition)
|
|
|
|
if err != nil {
|
2020-04-13 12:57:53 +02:00
|
|
|
return readers.MessagesPage{}, errors.Wrap(errReadMessages, err)
|
2019-04-25 00:18:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return readers.MessagesPage{
|
|
|
|
Total: total,
|
|
|
|
Offset: offset,
|
|
|
|
Limit: limit,
|
|
|
|
Messages: ret,
|
2019-05-07 15:59:18 +02:00
|
|
|
}, nil
|
2019-04-25 00:18:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (repo *influxRepository) count(condition string) (uint64, error) {
|
|
|
|
cmd := fmt.Sprintf(`SELECT COUNT(protocol) FROM messages WHERE %s`, condition)
|
|
|
|
q := influxdata.Query{
|
|
|
|
Command: cmd,
|
|
|
|
Database: repo.database,
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := repo.client.Query(q)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
if resp.Error() != nil {
|
|
|
|
return 0, resp.Error()
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Results) < 1 ||
|
|
|
|
len(resp.Results[0].Series) < 1 ||
|
|
|
|
len(resp.Results[0].Series[0].Values) < 1 {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
countIndex := 0
|
|
|
|
for i, col := range resp.Results[0].Series[0].Columns {
|
|
|
|
if col == countCol {
|
|
|
|
countIndex = i
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
result := resp.Results[0].Series[0].Values[0]
|
|
|
|
if len(result) < countIndex+1 {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
count, ok := result[countIndex].(json.Number)
|
|
|
|
if !ok {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return strconv.ParseUint(count.String(), 10, 64)
|
2018-08-25 12:48:03 +02:00
|
|
|
}
|
|
|
|
|
2019-03-15 18:38:07 +01:00
|
|
|
func fmtCondition(chanID string, query map[string]string) string {
|
|
|
|
condition := fmt.Sprintf(`channel='%s'`, chanID)
|
|
|
|
for name, value := range query {
|
|
|
|
switch name {
|
|
|
|
case
|
|
|
|
"channel",
|
|
|
|
"subtopic",
|
|
|
|
"publisher":
|
|
|
|
condition = fmt.Sprintf(`%s AND %s='%s'`, condition, name,
|
|
|
|
strings.Replace(value, "'", "\\'", -1))
|
|
|
|
case
|
|
|
|
"name",
|
|
|
|
"protocol":
|
|
|
|
condition = fmt.Sprintf(`%s AND "%s"='%s'`, condition, name,
|
|
|
|
strings.Replace(value, "\"", "\\\"", -1))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return condition
|
|
|
|
}
|
|
|
|
|
2018-11-05 19:18:51 +01:00
|
|
|
// ParseMessage and parseValues are util methods. Since InfluxDB client returns
|
|
|
|
// results in form of rows and columns, this obscure message conversion is needed
|
2020-04-01 21:22:13 +02:00
|
|
|
// to return actual []broker.Message from the query result.
|
2019-11-05 11:57:16 +01:00
|
|
|
func parseValues(value interface{}, name string, msg *senml.Message) {
|
|
|
|
if name == "sum" && value != nil {
|
|
|
|
if valSum, ok := value.(json.Number); ok {
|
|
|
|
sum, err := valSum.Float64()
|
2018-11-05 19:18:51 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-11-18 16:42:39 +01:00
|
|
|
|
2019-11-05 11:57:16 +01:00
|
|
|
msg.Sum = &sum
|
2018-11-05 19:18:51 +01:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2018-11-18 16:42:39 +01:00
|
|
|
|
|
|
|
if strings.HasSuffix(strings.ToLower(name), "value") {
|
2018-11-05 19:18:51 +01:00
|
|
|
switch value.(type) {
|
|
|
|
case bool:
|
2019-11-05 11:57:16 +01:00
|
|
|
v := value.(bool)
|
|
|
|
msg.BoolValue = &v
|
2018-11-05 19:18:51 +01:00
|
|
|
case json.Number:
|
|
|
|
num, err := value.(json.Number).Float64()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2019-11-05 11:57:16 +01:00
|
|
|
msg.Value = &num
|
2018-11-05 19:18:51 +01:00
|
|
|
case string:
|
2018-11-18 16:42:39 +01:00
|
|
|
if strings.HasPrefix(name, "string") {
|
2019-11-05 11:57:16 +01:00
|
|
|
v := value.(string)
|
|
|
|
msg.StringValue = &v
|
2018-11-05 19:18:51 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-11-18 16:42:39 +01:00
|
|
|
if strings.HasPrefix(name, "data") {
|
2019-11-05 11:57:16 +01:00
|
|
|
v := value.(string)
|
|
|
|
msg.DataValue = &v
|
2018-11-05 19:18:51 +01:00
|
|
|
}
|
|
|
|
}
|
2018-08-25 12:48:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-05 11:57:16 +01:00
|
|
|
func parseMessage(names []string, fields []interface{}) senml.Message {
|
|
|
|
m := senml.Message{}
|
2018-08-25 12:48:03 +02:00
|
|
|
v := reflect.ValueOf(&m).Elem()
|
|
|
|
for i, name := range names {
|
2018-11-05 19:18:51 +01:00
|
|
|
parseValues(fields[i], name, &m)
|
2018-11-18 16:42:39 +01:00
|
|
|
msgField := v.FieldByName(strings.Title(name))
|
2018-08-25 12:48:03 +02:00
|
|
|
if !msgField.IsValid() {
|
|
|
|
continue
|
|
|
|
}
|
2018-11-05 19:18:51 +01:00
|
|
|
|
2018-08-25 12:48:03 +02:00
|
|
|
f := msgField.Interface()
|
|
|
|
switch f.(type) {
|
|
|
|
case string:
|
|
|
|
if s, ok := fields[i].(string); ok {
|
|
|
|
msgField.SetString(s)
|
|
|
|
}
|
|
|
|
case float64:
|
2018-11-18 16:42:39 +01:00
|
|
|
if name == "time" {
|
2020-05-12 18:19:04 +02:00
|
|
|
t, err := time.Parse(time.RFC3339Nano, fields[i].(string))
|
2018-11-18 16:42:39 +01:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-05-12 18:19:04 +02:00
|
|
|
v := float64(t.UnixNano()) / float64(1e9)
|
2018-11-18 16:42:39 +01:00
|
|
|
msgField.SetFloat(v)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-11-05 19:18:51 +01:00
|
|
|
val, _ := strconv.ParseFloat(fields[i].(string), 64)
|
|
|
|
msgField.SetFloat(val)
|
2018-08-25 12:48:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return m
|
|
|
|
}
|