2021-02-01 00:10:16 +01:00
|
|
|
package influx
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
influx "github.com/influxdata/influxdb/client/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
type queryValues struct {
|
|
|
|
name string
|
2024-07-10 00:14:15 +02:00
|
|
|
values map[string][]any
|
2021-02-01 00:10:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func parseResult(r influx.Result) ([]queryValues, error) {
|
|
|
|
if len(r.Err) > 0 {
|
|
|
|
return nil, fmt.Errorf("result error: %s", r.Err)
|
|
|
|
}
|
|
|
|
qValues := make([]queryValues, len(r.Series))
|
|
|
|
for i, row := range r.Series {
|
2024-07-10 00:14:15 +02:00
|
|
|
values := make(map[string][]any, len(row.Values))
|
2021-02-01 00:10:16 +01:00
|
|
|
for _, value := range row.Values {
|
|
|
|
for idx, v := range value {
|
|
|
|
key := row.Columns[idx]
|
|
|
|
values[key] = append(values[key], v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
qValues[i] = queryValues{
|
|
|
|
name: row.Name,
|
|
|
|
values: values,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return qValues, nil
|
|
|
|
}
|
|
|
|
|
2024-07-10 00:14:15 +02:00
|
|
|
func toFloat64(v any) (float64, error) {
|
2021-02-01 00:10:16 +01:00
|
|
|
switch i := v.(type) {
|
|
|
|
case json.Number:
|
|
|
|
return i.Float64()
|
|
|
|
case float64:
|
|
|
|
return i, nil
|
|
|
|
case float32:
|
|
|
|
return float64(i), nil
|
|
|
|
case int64:
|
|
|
|
return float64(i), nil
|
|
|
|
case int32:
|
|
|
|
return float64(i), nil
|
|
|
|
case int:
|
|
|
|
return float64(i), nil
|
|
|
|
case uint64:
|
|
|
|
return float64(i), nil
|
|
|
|
case uint32:
|
|
|
|
return float64(i), nil
|
|
|
|
case uint:
|
|
|
|
return float64(i), nil
|
|
|
|
case string:
|
|
|
|
return strconv.ParseFloat(i, 64)
|
2021-10-18 09:29:34 +02:00
|
|
|
case bool:
|
|
|
|
if i {
|
|
|
|
return 1, nil
|
|
|
|
}
|
2021-10-18 14:10:44 +02:00
|
|
|
return 0, nil
|
2021-02-01 00:10:16 +01:00
|
|
|
default:
|
|
|
|
return 0, fmt.Errorf("unexpected value type %v", i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseDate(dateStr string) (int64, error) {
|
|
|
|
startTime, err := time.Parse(time.RFC3339, dateStr)
|
|
|
|
if err != nil {
|
|
|
|
return 0, fmt.Errorf("cannot parse %q: %s", dateStr, err)
|
|
|
|
}
|
|
|
|
return startTime.UnixNano() / 1e6, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func stringify(q influx.Query) string {
|
|
|
|
return fmt.Sprintf("command: %q; database: %q; retention: %q",
|
|
|
|
q.Command, q.Database, q.RetentionPolicy)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Series) unmarshal(v string) error {
|
|
|
|
noEscapeChars := strings.IndexByte(v, '\\') < 0
|
|
|
|
n := nextUnescapedChar(v, ',', noEscapeChars)
|
|
|
|
if n < 0 {
|
|
|
|
s.Measurement = unescapeTagValue(v, noEscapeChars)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
s.Measurement = unescapeTagValue(v[:n], noEscapeChars)
|
|
|
|
var err error
|
|
|
|
s.LabelPairs, err = unmarshalTags(v[n+1:], noEscapeChars)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to unmarhsal tags: %s", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func unmarshalTags(s string, noEscapeChars bool) ([]LabelPair, error) {
|
|
|
|
var result []LabelPair
|
|
|
|
for {
|
|
|
|
lp := LabelPair{}
|
|
|
|
n := nextUnescapedChar(s, ',', noEscapeChars)
|
|
|
|
if n < 0 {
|
|
|
|
if err := lp.unmarshal(s, noEscapeChars); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(lp.Name) == 0 || len(lp.Value) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
result = append(result, lp)
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
if err := lp.unmarshal(s[:n], noEscapeChars); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
s = s[n+1:]
|
|
|
|
if len(lp.Name) == 0 || len(lp.Value) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
result = append(result, lp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (lp *LabelPair) unmarshal(s string, noEscapeChars bool) error {
|
|
|
|
n := nextUnescapedChar(s, '=', noEscapeChars)
|
|
|
|
if n < 0 {
|
|
|
|
return fmt.Errorf("missing tag value for %q", s)
|
|
|
|
}
|
|
|
|
lp.Name = unescapeTagValue(s[:n], noEscapeChars)
|
|
|
|
lp.Value = unescapeTagValue(s[n+1:], noEscapeChars)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func unescapeTagValue(s string, noEscapeChars bool) string {
|
|
|
|
if noEscapeChars {
|
|
|
|
// Fast path - no escape chars.
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
n := strings.IndexByte(s, '\\')
|
|
|
|
if n < 0 {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
// Slow path. Remove escape chars.
|
|
|
|
dst := make([]byte, 0, len(s))
|
|
|
|
for {
|
|
|
|
dst = append(dst, s[:n]...)
|
|
|
|
s = s[n+1:]
|
|
|
|
if len(s) == 0 {
|
|
|
|
return string(append(dst, '\\'))
|
|
|
|
}
|
|
|
|
ch := s[0]
|
|
|
|
if ch != ' ' && ch != ',' && ch != '=' && ch != '\\' {
|
|
|
|
dst = append(dst, '\\')
|
|
|
|
}
|
|
|
|
dst = append(dst, ch)
|
|
|
|
s = s[1:]
|
|
|
|
n = strings.IndexByte(s, '\\')
|
|
|
|
if n < 0 {
|
|
|
|
return string(append(dst, s...))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func nextUnescapedChar(s string, ch byte, noEscapeChars bool) int {
|
|
|
|
if noEscapeChars {
|
|
|
|
// Fast path: just search for ch in s, since s has no escape chars.
|
|
|
|
return strings.IndexByte(s, ch)
|
|
|
|
}
|
|
|
|
|
|
|
|
sOrig := s
|
|
|
|
again:
|
|
|
|
n := strings.IndexByte(s, ch)
|
|
|
|
if n < 0 {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
if n == 0 {
|
|
|
|
return len(sOrig) - len(s) + n
|
|
|
|
}
|
|
|
|
if s[n-1] != '\\' {
|
|
|
|
return len(sOrig) - len(s) + n
|
|
|
|
}
|
|
|
|
nOrig := n
|
|
|
|
slashes := 0
|
|
|
|
for n > 0 && s[n-1] == '\\' {
|
|
|
|
slashes++
|
|
|
|
n--
|
|
|
|
}
|
|
|
|
if slashes&1 == 0 {
|
|
|
|
return len(sOrig) - len(s) + nOrig
|
|
|
|
}
|
|
|
|
s = s[nOrig+1:]
|
|
|
|
goto again
|
|
|
|
}
|