|
@@ -1,4 +1,4 @@
|
|
|
-// Copyright 2022 EMQ Technologies Co., Ltd.
|
|
|
+// Copyright 2022-2023 EMQ Technologies Co., Ltd.
|
|
|
//
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
// you may not use this file except in compliance with the License.
|
|
@@ -25,9 +25,15 @@ import (
|
|
|
"text/template"
|
|
|
)
|
|
|
|
|
|
-type TransFunc func(interface{}) ([]byte, bool, error)
|
|
|
+// TransFunc is the function to transform data
|
|
|
|
|
|
-func GenTransform(dt string, format string, schemaId string, delimiter string) (TransFunc, error) {
|
|
|
+// The second parameter indicates whether to select fields based on the fields property.
|
|
|
+// If it is false, then after the dataTemplate, output the result directly.
|
|
|
+// If it is true, then after the dataTemplate, select the fields based on the fields property.
|
|
|
+
|
|
|
+type TransFunc func(interface{}, bool) ([]byte, bool, error)
|
|
|
+
|
|
|
+func GenTransform(dt string, format string, schemaId string, delimiter string, fields []string) (TransFunc, error) {
|
|
|
var (
|
|
|
tp *template.Template = nil
|
|
|
c message.Converter
|
|
@@ -44,6 +50,11 @@ func GenTransform(dt string, format string, schemaId string, delimiter string) (
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
+ case message.FormatJson:
|
|
|
+ c, err = converter.GetOrCreateConverter(&ast.Options{FORMAT: format})
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if dt != "" {
|
|
@@ -53,10 +64,11 @@ func GenTransform(dt string, format string, schemaId string, delimiter string) (
|
|
|
}
|
|
|
tp = temp
|
|
|
}
|
|
|
- return func(d interface{}) ([]byte, bool, error) {
|
|
|
+ return func(d interface{}, s bool) ([]byte, bool, error) {
|
|
|
var (
|
|
|
bs []byte
|
|
|
transformed bool
|
|
|
+ selected bool
|
|
|
)
|
|
|
if tp != nil {
|
|
|
var output bytes.Buffer
|
|
@@ -67,15 +79,39 @@ func GenTransform(dt string, format string, schemaId string, delimiter string) (
|
|
|
bs = output.Bytes()
|
|
|
transformed = true
|
|
|
}
|
|
|
+ // just for sinks like tdengine and sql.
|
|
|
+ if !s {
|
|
|
+ if transformed {
|
|
|
+ return bs, true, nil
|
|
|
+ }
|
|
|
+ outBytes, err := json.Marshal(d)
|
|
|
+ return outBytes, false, err
|
|
|
+ } else {
|
|
|
+ // Consider that if only the dataTemplate is needed, and the data after trans cannot be converted into map[string]interface
|
|
|
+ var m interface{}
|
|
|
+ var err error
|
|
|
+ if transformed {
|
|
|
+ m, err = SelectMap(bs, fields)
|
|
|
+ } else {
|
|
|
+ m, err = SelectMap(d, fields)
|
|
|
+ }
|
|
|
+ if err != nil && err.Error() != "fields cannot be empty" {
|
|
|
+ return nil, false, fmt.Errorf("fail to decode data %s after applying dataTemplate for error %v", string(bs), err)
|
|
|
+ } else if err == nil {
|
|
|
+ d = m
|
|
|
+ selected = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
switch format {
|
|
|
case message.FormatJson:
|
|
|
- if transformed {
|
|
|
- return bs, transformed, nil
|
|
|
+ if transformed && !selected {
|
|
|
+ return bs, true, nil
|
|
|
}
|
|
|
- j, err := json.Marshal(d)
|
|
|
- return j, false, err
|
|
|
+ outBytes, err := c.Encode(d)
|
|
|
+ return outBytes, transformed || selected, err
|
|
|
case message.FormatProtobuf, message.FormatCustom, message.FormatDelimited:
|
|
|
- if transformed {
|
|
|
+ if transformed && !selected {
|
|
|
m := make(map[string]interface{})
|
|
|
err := json.Unmarshal(bs, &m)
|
|
|
if err != nil {
|
|
@@ -83,8 +119,9 @@ func GenTransform(dt string, format string, schemaId string, delimiter string) (
|
|
|
}
|
|
|
d = m
|
|
|
}
|
|
|
- b, err := c.Encode(d)
|
|
|
- return b, transformed, err
|
|
|
+ // TODO: if headers are defined by user, find a way to keep the order
|
|
|
+ outBytes, err := c.Encode(d)
|
|
|
+ return outBytes, transformed || selected, err
|
|
|
default: // should not happen
|
|
|
return nil, false, fmt.Errorf("unsupported format %v", format)
|
|
|
}
|
|
@@ -94,3 +131,56 @@ func GenTransform(dt string, format string, schemaId string, delimiter string) (
|
|
|
func GenTp(dt string) (*template.Template, error) {
|
|
|
return template.New("sink").Funcs(conf.FuncMap).Parse(dt)
|
|
|
}
|
|
|
+
|
|
|
+// SelectMap select fields from input map or array of map.
|
|
|
+// If you do not need to convert data to []byte, you can use this function directly. Otherwise, use TransFunc.
|
|
|
+func SelectMap(input interface{}, fields []string) (interface{}, error) {
|
|
|
+ if len(fields) == 0 {
|
|
|
+ return input, fmt.Errorf("fields cannot be empty")
|
|
|
+ }
|
|
|
+
|
|
|
+ if _, ok := input.([]byte); ok {
|
|
|
+ var m map[string]interface{}
|
|
|
+ err := json.Unmarshal(input.([]byte), &m)
|
|
|
+ if err != nil {
|
|
|
+ return input, fmt.Errorf("fail to decode data %s for error %v", string(input.([]byte)), err)
|
|
|
+ }
|
|
|
+ input = m
|
|
|
+ }
|
|
|
+
|
|
|
+ outputs := make([]map[string]interface{}, 0)
|
|
|
+ switch input.(type) {
|
|
|
+ case map[string]interface{}:
|
|
|
+ output := make(map[string]interface{})
|
|
|
+ for _, field := range fields {
|
|
|
+ output[field] = input.(map[string]interface{})[field]
|
|
|
+ }
|
|
|
+ return output, nil
|
|
|
+ case []interface{}:
|
|
|
+ for _, v := range input.([]interface{}) {
|
|
|
+ output := make(map[string]interface{})
|
|
|
+ if out, ok := v.(map[string]interface{}); !ok {
|
|
|
+ return input, fmt.Errorf("unsupported type %v", input)
|
|
|
+ } else {
|
|
|
+ for _, field := range fields {
|
|
|
+ output[field] = out[field]
|
|
|
+ }
|
|
|
+ outputs = append(outputs, output)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return outputs, nil
|
|
|
+ case []map[string]interface{}:
|
|
|
+ for _, v := range input.([]map[string]interface{}) {
|
|
|
+ output := make(map[string]interface{})
|
|
|
+ for _, field := range fields {
|
|
|
+ output[field] = v[field]
|
|
|
+ }
|
|
|
+ outputs = append(outputs, output)
|
|
|
+ }
|
|
|
+ return outputs, nil
|
|
|
+ default:
|
|
|
+ return input, fmt.Errorf("unsupported type %v", input)
|
|
|
+ }
|
|
|
+
|
|
|
+ return input, nil
|
|
|
+}
|