mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 15:36:48 +00:00
* fix: use lower and convert re2 to string in fulltext * fix: minor error change * fix: address comments --------- Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
197 lines
5.4 KiB
Go
197 lines
5.4 KiB
Go
package querybuilder
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/SigNoz/signoz/pkg/errors"
|
|
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
|
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
|
"github.com/huandu/go-sqlbuilder"
|
|
"golang.org/x/exp/maps"
|
|
)
|
|
|
|
func CollisionHandledFinalExpr(
|
|
ctx context.Context,
|
|
field *telemetrytypes.TelemetryFieldKey,
|
|
fm qbtypes.FieldMapper,
|
|
cb qbtypes.ConditionBuilder,
|
|
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
|
requiredDataType telemetrytypes.FieldDataType,
|
|
jsonBodyPrefix string,
|
|
jsonKeyToKey qbtypes.JsonKeyToFieldFunc,
|
|
) (string, []any, error) {
|
|
|
|
if requiredDataType != telemetrytypes.FieldDataTypeString &&
|
|
requiredDataType != telemetrytypes.FieldDataTypeFloat64 {
|
|
return "", nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "unsupported data type %s", requiredDataType)
|
|
}
|
|
|
|
var dummyValue any
|
|
if requiredDataType == telemetrytypes.FieldDataTypeFloat64 {
|
|
dummyValue = 0.0
|
|
} else {
|
|
dummyValue = ""
|
|
}
|
|
|
|
var stmts []string
|
|
var allArgs []any
|
|
|
|
addCondition := func(key *telemetrytypes.TelemetryFieldKey) error {
|
|
sb := sqlbuilder.NewSelectBuilder()
|
|
condition, err := cb.ConditionFor(ctx, key, qbtypes.FilterOperatorExists, nil, sb)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sb.Where(condition)
|
|
|
|
expr, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
|
|
expr = strings.TrimPrefix(expr, "WHERE ")
|
|
stmts = append(stmts, expr)
|
|
allArgs = append(allArgs, args...)
|
|
return nil
|
|
}
|
|
|
|
colName, err := fm.FieldFor(ctx, field)
|
|
if errors.Is(err, qbtypes.ErrColumnNotFound) {
|
|
// the key didn't have the right context to be added to the query
|
|
// we try to use the context we know of
|
|
keysForField := keys[field.Name]
|
|
|
|
if len(keysForField) == 0 {
|
|
// check if the key exists with {fieldContext}.{key}
|
|
// because the context could be legitimate prefix in user data, example `metric.max`
|
|
keyWithContext := fmt.Sprintf("%s.%s", field.FieldContext.StringValue(), field.Name)
|
|
if len(keys[keyWithContext]) > 0 {
|
|
keysForField = keys[keyWithContext]
|
|
}
|
|
}
|
|
|
|
if len(keysForField) == 0 {
|
|
// - the context is not provided
|
|
// - there are not keys for the field
|
|
// - it is not a static field
|
|
// - the next best thing to do is see if there is a typo
|
|
// and suggest a correction
|
|
correction, found := telemetrytypes.SuggestCorrection(field.Name, maps.Keys(keys))
|
|
if found {
|
|
// we found a close match, in the error message send the suggestion
|
|
return "", nil, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, correction)
|
|
} else {
|
|
// not even a close match, return an error
|
|
return "", nil, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "field `%s` not found", field.Name)
|
|
}
|
|
} else {
|
|
for _, key := range keysForField {
|
|
err := addCondition(key)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
colName, _ = fm.FieldFor(ctx, key)
|
|
colName, _ = telemetrytypes.DataTypeCollisionHandledFieldName(key, dummyValue, colName)
|
|
stmts = append(stmts, colName)
|
|
}
|
|
}
|
|
} else {
|
|
err := addCondition(field)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
if strings.HasPrefix(field.Name, jsonBodyPrefix) && jsonBodyPrefix != "" && jsonKeyToKey != nil {
|
|
// TODO(nitya): enable group by on body column?
|
|
return "", nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "Group by/Aggregation isn't available for the body column")
|
|
// colName, _ = jsonKeyToKey(context.Background(), field, qbtypes.FilterOperatorUnknown, dummyValue)
|
|
} else {
|
|
colName, _ = telemetrytypes.DataTypeCollisionHandledFieldName(field, dummyValue, colName)
|
|
}
|
|
|
|
stmts = append(stmts, colName)
|
|
}
|
|
|
|
for idx := range stmts {
|
|
stmts[idx] = sqlbuilder.Escape(stmts[idx])
|
|
}
|
|
|
|
multiIfStmt := fmt.Sprintf("multiIf(%s, NULL)", strings.Join(stmts, ", "))
|
|
|
|
return multiIfStmt, allArgs, nil
|
|
}
|
|
|
|
func GroupByKeys(keys []qbtypes.GroupByKey) []string {
|
|
k := []string{}
|
|
for _, key := range keys {
|
|
k = append(k, "`"+key.Name+"`")
|
|
}
|
|
return k
|
|
}
|
|
|
|
func FormatValueForContains(value any) string {
|
|
if value == nil {
|
|
return ""
|
|
}
|
|
|
|
switch v := value.(type) {
|
|
case string:
|
|
return v
|
|
case []byte:
|
|
return string(v)
|
|
|
|
case json.Number:
|
|
return v.String()
|
|
|
|
case float64:
|
|
if v == math.Trunc(v) && v >= -1e15 && v <= 1e15 {
|
|
return fmt.Sprintf("%.0f", v)
|
|
}
|
|
return strconv.FormatFloat(v, 'f', -1, 64)
|
|
|
|
case float32:
|
|
return strconv.FormatFloat(float64(v), 'f', -1, 32)
|
|
|
|
case int, int8, int16, int32, int64:
|
|
return fmt.Sprintf("%d", v)
|
|
|
|
case uint, uint8, uint16, uint32, uint64:
|
|
return fmt.Sprintf("%d", v)
|
|
|
|
case bool:
|
|
return strconv.FormatBool(v)
|
|
|
|
case fmt.Stringer:
|
|
return v.String()
|
|
|
|
default:
|
|
// fallback - try to convert through reflection
|
|
rv := reflect.ValueOf(value)
|
|
switch rv.Kind() {
|
|
case reflect.Float32, reflect.Float64:
|
|
f := rv.Float()
|
|
if f == math.Trunc(f) && f >= -1e15 && f <= 1e15 {
|
|
return fmt.Sprintf("%.0f", f)
|
|
}
|
|
return strconv.FormatFloat(f, 'f', -1, 64)
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return strconv.FormatInt(rv.Int(), 10)
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
return strconv.FormatUint(rv.Uint(), 10)
|
|
default:
|
|
return fmt.Sprintf("%v", value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func FormatFullTextSearch(input string) string {
|
|
if _, err := regexp.Compile(input); err != nil {
|
|
// Not a valid regex -> treat as literal substring
|
|
return regexp.QuoteMeta(input)
|
|
}
|
|
return input
|
|
}
|