1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-04-28 13:48:49 +08:00
Washington Kigani Kamadi e2992cbede
NOISSUE - Change import name aliases (#1868)
* Change import name aliases

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Change import name aliases

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Change import aliases

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Remove unused aliases

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

Fix aliases

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

FIx errors

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

Fix error

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

FIx merge

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

FIx merge

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

FIx merge

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix import alias

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix errors

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix linter

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix linter

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix import

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Add linter to CI pipeline

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Changes

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Remove unused aliases

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix merge issues

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix gci

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix gci

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix gci

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Add gofumpt

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Remove multiple gofupmt in CI

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Remove unnecessary changes

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix linter

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix CI pipeline

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

---------

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>
2023-08-11 11:30:25 +02:00

313 lines
7.8 KiB
Go

// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package influxdb
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"unicode"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
"github.com/mainflux/mainflux/pkg/errors"
jsont "github.com/mainflux/mainflux/pkg/transformers/json"
"github.com/mainflux/mainflux/pkg/transformers/senml"
"github.com/mainflux/mainflux/readers"
)
const (
// Measurement for SenML messages.
defMeasurement = "messages"
)
var _ readers.MessageRepository = (*influxRepository)(nil)
var errResultTime = errors.New("invalid result time")
type RepoConfig struct {
Bucket string
Org string
}
type influxRepository struct {
cfg RepoConfig
client influxdb2.Client
}
// New returns new InfluxDB reader.
func New(client influxdb2.Client, repoCfg RepoConfig) readers.MessageRepository {
return &influxRepository{
repoCfg,
client,
}
}
func (repo *influxRepository) ReadAll(chanID string, rpm readers.PageMetadata) (readers.MessagesPage, error) {
format := defMeasurement
if rpm.Format != "" {
format = rpm.Format
}
queryAPI := repo.client.QueryAPI(repo.cfg.Org)
condition, timeRange := fmtCondition(chanID, rpm)
query := fmt.Sprintf(`
import "influxdata/influxdb/v1"
import "strings"
from(bucket: "%s")
%s
|> v1.fieldsAsCols()
|> group()
|> filter(fn: (r) => r._measurement == "%s")
%s
|> sort(columns: ["_time"], desc: true)
|> limit(n:%d,offset:%d)
|> yield(name: "sort")`,
repo.cfg.Bucket,
timeRange,
format,
condition,
rpm.Limit, rpm.Offset,
)
resp, err := queryAPI.Query(context.Background(), query)
if err != nil {
return readers.MessagesPage{}, errors.Wrap(readers.ErrReadMessages, err)
}
var messages []readers.Message
var valueMap map[string]interface{}
for resp.Next() {
valueMap = resp.Record().Values()
msg, err := parseMessage(format, valueMap)
if err != nil {
return readers.MessagesPage{}, err
}
messages = append(messages, msg)
}
if resp.Err() != nil {
return readers.MessagesPage{}, errors.Wrap(readers.ErrReadMessages, resp.Err())
}
total, err := repo.count(format, condition, timeRange)
if err != nil {
return readers.MessagesPage{}, errors.Wrap(readers.ErrReadMessages, err)
}
page := readers.MessagesPage{
PageMetadata: rpm,
Total: total,
Messages: messages,
}
return page, nil
}
func (repo *influxRepository) count(measurement, condition string, timeRange string) (uint64, error) {
cmd := fmt.Sprintf(`
import "influxdata/influxdb/v1"
import "strings"
from(bucket: "%s")
%s
|> v1.fieldsAsCols()
|> filter(fn: (r) => r._measurement == "%s")
%s
|> group()
|> count(column:"_measurement")
|> yield(name: "count")
`,
repo.cfg.Bucket,
timeRange,
measurement,
condition)
queryAPI := repo.client.QueryAPI(repo.cfg.Org)
resp, err := queryAPI.Query(context.Background(), cmd)
if err != nil {
return 0, err
}
switch resp.Next() {
case true:
valueMap := resp.Record().Values()
val, ok := valueMap["_measurement"].(int64)
if !ok {
return 0, nil
}
return uint64(val), nil
default:
// same as no rows.
return 0, nil
}
}
func fmtCondition(chanID string, rpm readers.PageMetadata) (string, string) {
var timeRange string
var sb strings.Builder
sb.WriteString(fmt.Sprintf(`|> filter(fn: (r) => r["channel"] == "%s" )`, chanID))
var query map[string]interface{}
meta, err := json.Marshal(rpm)
if err != nil {
return sb.String(), timeRange
}
if err := json.Unmarshal(meta, &query); err != nil {
return sb.String(), timeRange
}
// range(start:...) is a must for FluxQL syntax.
from := `start: time(v:0)`
if value, ok := query["from"]; ok {
fromValue := int64(value.(float64)*1e9) - 1
from = fmt.Sprintf(`start: time(v: %d )`, fromValue)
}
// range(...,stop:) is an option for FluxQL syntax.
to := ""
if value, ok := query["to"]; ok {
toValue := int64(value.(float64) * 1e9)
to = fmt.Sprintf(`, stop: time(v: %d )`, toValue)
}
// timeRange returned separately because
// in FluxQL time range must be at the
// beginning of the query.
timeRange = fmt.Sprintf(`|> range(%s %s)`, from, to)
for name, value := range query {
switch name {
case
"channel",
"subtopic",
"publisher",
"name",
"protocol":
sb.WriteString(fmt.Sprintf(`|> filter(fn: (r) => r.%s == "%s" )`, name, value))
case "v":
comparator := readers.ParseValueComparator(query)
// flux eq comparator is different
if comparator == "=" {
comparator = "=="
}
sb.WriteString(`|> filter(fn: (r) => exists r.value)`)
sb.WriteString(fmt.Sprintf(`|> filter(fn: (r) => r.value %s %v)`, comparator, value))
case "vb":
sb.WriteString(`|> filter(fn: (r) => exists r.boolValue)`)
sb.WriteString(fmt.Sprintf(`|> filter(fn: (r) => r.boolValue == %v)`, value))
case "vs":
comparator := readers.ParseValueComparator(query)
sb.WriteString(`|> filter(fn: (r) => exists r.stringValue)`)
switch comparator {
case "=":
sb.WriteString(fmt.Sprintf(`|> filter(fn: (r) => r.stringValue == "%s")`, value))
case "<":
sb.WriteString(fmt.Sprintf(`|> filter(fn: (r) => strings.containsStr(v: "%s", substr: r.stringValue) == true)`, value))
sb.WriteString(fmt.Sprintf(`|> filter(fn: (r) => r.stringValue !="%s")`, value))
case "<=":
sb.WriteString(fmt.Sprintf(`|> filter(fn: (r) => strings.containsStr(v: "%s", substr: r.stringValue) == true)`, value))
case ">":
sb.WriteString(fmt.Sprintf(`|> filter(fn: (r) => strings.containsStr(v: r.stringValue, substr: "%s") == true)`, value))
sb.WriteString(fmt.Sprintf(`|> filter(fn: (r) => r.stringValue != "%s")`, value))
case ">=":
sb.WriteString(fmt.Sprintf(`|> filter(fn: (r) => strings.containsStr(v: r.stringValue, substr: "%s") == true)`, value))
}
case "vd":
comparator := readers.ParseValueComparator(query)
if comparator == "=" {
comparator = "=="
}
sb.WriteString(`|> filter(fn: (r) => exists r.dataValue)`)
sb.WriteString(fmt.Sprintf(`|> filter(fn: (r) => r.dataValue%s"%s")`, comparator, value))
}
}
return sb.String(), timeRange
}
func parseMessage(measurement string, valueMap map[string]interface{}) (interface{}, error) {
switch measurement {
case defMeasurement:
return parseSenml(valueMap)
default:
return parseJSON(valueMap)
}
}
func underscore(name string) string {
var buff []rune
idx := 0
for i, c := range name {
if unicode.IsUpper(c) {
buff = append(buff, []rune(name[idx:i])...)
buff = append(buff, []rune{'_', unicode.ToLower(c)}...)
idx = i + 1
continue
}
}
buff = append(buff, []rune(name[idx:])...)
return string(buff)
}
func parseSenml(valueMap map[string]interface{}) (interface{}, error) {
msg := make(map[string]interface{})
for k, v := range valueMap {
k = underscore(k)
if k == "_time" {
k = "time"
t, ok := v.(time.Time)
if !ok {
return nil, errResultTime
}
v := float64(t.UnixNano()) / 1e9
msg[k] = v
continue
}
msg[k] = v
}
data, err := json.Marshal(msg)
if err != nil {
return nil, err
}
senmlMsg := senml.Message{}
if err := json.Unmarshal(data, &senmlMsg); err != nil {
return nil, err
}
return senmlMsg, nil
}
func parseJSON(valueMap map[string]interface{}) (interface{}, error) {
ret := make(map[string]interface{})
pld := make(map[string]interface{})
for name, field := range valueMap {
switch name {
case "channel", "created", "subtopic", "publisher", "protocol":
ret[name] = field
case "_time":
name = "time"
t, ok := field.(time.Time)
if !ok {
return nil, errResultTime
}
v := float64(t.UnixNano()) / 1e9
ret[name] = v
continue
case "table", "_start", "_stop", "result", "_measurement":
default:
v := field
if val, ok := v.(json.Number); ok {
var err error
v, err = val.Float64()
if err != nil {
return nil, err
}
}
pld[name] = v
}
}
ret["payload"] = jsont.ParseFlat(pld)
return ret, nil
}