mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-01 09:52:00 +08:00
chore: add condition builder for span index v3 (#7556)
This commit is contained in:
parent
ba6f31b1c3
commit
0b1faec092
352
pkg/telemetrytraces/condition_builder.go
Normal file
352
pkg/telemetrytraces/condition_builder.go
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
package telemetrytraces
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
schema "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
||||||
|
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||||
|
"github.com/huandu/go-sqlbuilder"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
indexV3Columns = map[string]*schema.Column{
|
||||||
|
"ts_bucket_start": {Name: "ts_bucket_start", Type: schema.ColumnTypeUInt64},
|
||||||
|
"resource_fingerprint": {Name: "resource_fingerprint", Type: schema.ColumnTypeString},
|
||||||
|
|
||||||
|
// intrinsic columns
|
||||||
|
"timestamp": {Name: "timestamp", Type: schema.DateTime64ColumnType{Precision: 9, Timezone: "UTC"}},
|
||||||
|
"trace_id": {Name: "trace_id", Type: schema.FixedStringColumnType{Length: 32}},
|
||||||
|
"span_id": {Name: "span_id", Type: schema.ColumnTypeString},
|
||||||
|
"trace_state": {Name: "trace_state", Type: schema.ColumnTypeString},
|
||||||
|
"parent_span_id": {Name: "parent_span_id", Type: schema.ColumnTypeString},
|
||||||
|
"flags": {Name: "flags", Type: schema.ColumnTypeUInt32},
|
||||||
|
"name": {Name: "name", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"kind": {Name: "kind", Type: schema.ColumnTypeInt8},
|
||||||
|
"kind_string": {Name: "kind_string", Type: schema.ColumnTypeString},
|
||||||
|
"duration_nano": {Name: "duration_nano", Type: schema.ColumnTypeUInt64},
|
||||||
|
"status_code": {Name: "status_code", Type: schema.ColumnTypeInt16},
|
||||||
|
"status_message": {Name: "status_message", Type: schema.ColumnTypeString},
|
||||||
|
"status_code_string": {Name: "status_code_string", Type: schema.ColumnTypeString},
|
||||||
|
|
||||||
|
// attributes columns
|
||||||
|
"attributes_string": {Name: "attributes_string", Type: schema.MapColumnType{
|
||||||
|
KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
|
||||||
|
ValueType: schema.ColumnTypeString,
|
||||||
|
}},
|
||||||
|
"attributes_number": {Name: "attributes_number", Type: schema.MapColumnType{
|
||||||
|
KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
|
||||||
|
ValueType: schema.ColumnTypeFloat64,
|
||||||
|
}},
|
||||||
|
"attributes_bool": {Name: "attributes_bool", Type: schema.MapColumnType{
|
||||||
|
KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
|
||||||
|
ValueType: schema.ColumnTypeBool,
|
||||||
|
}},
|
||||||
|
"resources_string": {Name: "resources_string", Type: schema.MapColumnType{
|
||||||
|
KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
|
||||||
|
ValueType: schema.ColumnTypeString,
|
||||||
|
}},
|
||||||
|
|
||||||
|
"events": {Name: "events", Type: schema.ArrayColumnType{
|
||||||
|
ElementType: schema.ColumnTypeString,
|
||||||
|
}},
|
||||||
|
"links": {Name: "links", Type: schema.ColumnTypeString},
|
||||||
|
// derived columns
|
||||||
|
"response_status_code": {Name: "response_status_code", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"external_http_url": {Name: "external_http_url", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"http_url": {Name: "http_url", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"external_http_method": {Name: "external_http_method", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"http_method": {Name: "http_method", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"http_host": {Name: "http_host", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"db_name": {Name: "db_name", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"db_operation": {Name: "db_operation", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"has_error": {Name: "has_error", Type: schema.ColumnTypeBool},
|
||||||
|
"is_remote": {Name: "is_remote", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
// materialized columns
|
||||||
|
"resource_string_service$$name": {Name: "resource_string_service$$name", Type: schema.ColumnTypeString},
|
||||||
|
"attribute_string_http$$route": {Name: "attribute_string_http$$route", Type: schema.ColumnTypeString},
|
||||||
|
"attribute_string_messaging$$system": {Name: "attribute_string_messaging$$system", Type: schema.ColumnTypeString},
|
||||||
|
"attribute_string_messaging$$operation": {Name: "attribute_string_messaging$$operation", Type: schema.ColumnTypeString},
|
||||||
|
"attribute_string_db$$system": {Name: "attribute_string_db$$system", Type: schema.ColumnTypeString},
|
||||||
|
"attribute_string_rpc$$system": {Name: "attribute_string_rpc$$system", Type: schema.ColumnTypeString},
|
||||||
|
"attribute_string_rpc$$service": {Name: "attribute_string_rpc$$service", Type: schema.ColumnTypeString},
|
||||||
|
"attribute_string_rpc$$method": {Name: "attribute_string_rpc$$method", Type: schema.ColumnTypeString},
|
||||||
|
"attribute_string_peer$$service": {Name: "attribute_string_peer$$service", Type: schema.ColumnTypeString},
|
||||||
|
|
||||||
|
// deprecated intrinsic columns
|
||||||
|
"traceID": {Name: "traceID", Type: schema.FixedStringColumnType{Length: 32}},
|
||||||
|
"spanID": {Name: "spanID", Type: schema.ColumnTypeString},
|
||||||
|
"parentSpanID": {Name: "parentSpanID", Type: schema.ColumnTypeString},
|
||||||
|
"spanKind": {Name: "spanKind", Type: schema.ColumnTypeString},
|
||||||
|
"durationNano": {Name: "durationNano", Type: schema.ColumnTypeUInt64},
|
||||||
|
"statusCode": {Name: "statusCode", Type: schema.ColumnTypeInt16},
|
||||||
|
"statusMessage": {Name: "statusMessage", Type: schema.ColumnTypeString},
|
||||||
|
"statusCodeString": {Name: "statusCodeString", Type: schema.ColumnTypeString},
|
||||||
|
|
||||||
|
// deprecated derived columns
|
||||||
|
"references": {Name: "references", Type: schema.ColumnTypeString},
|
||||||
|
"responseStatusCode": {Name: "responseStatusCode", Type: schema.ColumnTypeString},
|
||||||
|
"externalHttpUrl": {Name: "externalHttpUrl", Type: schema.ColumnTypeString},
|
||||||
|
"httpUrl": {Name: "httpUrl", Type: schema.ColumnTypeString},
|
||||||
|
"externalHttpMethod": {Name: "externalHttpMethod", Type: schema.ColumnTypeString},
|
||||||
|
"httpMethod": {Name: "httpMethod", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"httpHost": {Name: "httpHost", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"dbName": {Name: "dbName", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"dbOperation": {Name: "dbOperation", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"hasError": {Name: "hasError", Type: schema.ColumnTypeBool},
|
||||||
|
"isRemote": {Name: "isRemote", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"serviceName": {Name: "serviceName", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"httpRoute": {Name: "httpRoute", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"msgSystem": {Name: "msgSystem", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"msgOperation": {Name: "msgOperation", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"dbSystem": {Name: "dbSystem", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"rpcSystem": {Name: "rpcSystem", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"rpcService": {Name: "rpcService", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"rpcMethod": {Name: "rpcMethod", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
"peerService": {Name: "peerService", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||||
|
|
||||||
|
// materialized exists columns
|
||||||
|
"resource_string_service$$name_exists": {Name: "resource_string_service$$name_exists", Type: schema.ColumnTypeBool},
|
||||||
|
"attribute_string_http$$route_exists": {Name: "attribute_string_http$$route_exists", Type: schema.ColumnTypeBool},
|
||||||
|
"attribute_string_messaging$$system_exists": {Name: "attribute_string_messaging$$system_exists", Type: schema.ColumnTypeBool},
|
||||||
|
"attribute_string_messaging$$operation_exists": {Name: "attribute_string_messaging$$operation_exists", Type: schema.ColumnTypeBool},
|
||||||
|
"attribute_string_db$$system_exists": {Name: "attribute_string_db$$system_exists", Type: schema.ColumnTypeBool},
|
||||||
|
"attribute_string_rpc$$system_exists": {Name: "attribute_string_rpc$$system_exists", Type: schema.ColumnTypeBool},
|
||||||
|
"attribute_string_rpc$$service_exists": {Name: "attribute_string_rpc$$service_exists", Type: schema.ColumnTypeBool},
|
||||||
|
"attribute_string_rpc$$method_exists": {Name: "attribute_string_rpc$$method_exists", Type: schema.ColumnTypeBool},
|
||||||
|
"attribute_string_peer$$service_exists": {Name: "attribute_string_peer$$service_exists", Type: schema.ColumnTypeBool},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// interface check
|
||||||
|
var _ qbtypes.ConditionBuilder = &conditionBuilder{}
|
||||||
|
|
||||||
|
type conditionBuilder struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConditionBuilder() qbtypes.ConditionBuilder {
|
||||||
|
return &conditionBuilder{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conditionBuilder) GetColumn(ctx context.Context, key *telemetrytypes.TelemetryFieldKey) (*schema.Column, error) {
|
||||||
|
|
||||||
|
switch key.FieldContext {
|
||||||
|
case telemetrytypes.FieldContextResource:
|
||||||
|
return indexV3Columns["resources_string"], nil
|
||||||
|
case telemetrytypes.FieldContextScope:
|
||||||
|
// we don't have scope data stored in the spans yet
|
||||||
|
return nil, qbtypes.ErrColumnNotFound
|
||||||
|
case telemetrytypes.FieldContextAttribute:
|
||||||
|
switch key.FieldDataType {
|
||||||
|
case telemetrytypes.FieldDataTypeString:
|
||||||
|
return indexV3Columns["attributes_string"], nil
|
||||||
|
case telemetrytypes.FieldDataTypeInt64, telemetrytypes.FieldDataTypeFloat64, telemetrytypes.FieldDataTypeNumber:
|
||||||
|
return indexV3Columns["attributes_number"], nil
|
||||||
|
case telemetrytypes.FieldDataTypeBool:
|
||||||
|
return indexV3Columns["attributes_bool"], nil
|
||||||
|
}
|
||||||
|
case telemetrytypes.FieldContextSpan:
|
||||||
|
col, ok := indexV3Columns[key.Name]
|
||||||
|
if !ok {
|
||||||
|
return nil, qbtypes.ErrColumnNotFound
|
||||||
|
}
|
||||||
|
return col, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, qbtypes.ErrColumnNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conditionBuilder) GetTableFieldName(ctx context.Context, key *telemetrytypes.TelemetryFieldKey) (string, error) {
|
||||||
|
column, err := c.GetColumn(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch column.Type {
|
||||||
|
case schema.ColumnTypeString,
|
||||||
|
schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
|
||||||
|
schema.ColumnTypeUInt64,
|
||||||
|
schema.ColumnTypeUInt32,
|
||||||
|
schema.ColumnTypeInt8,
|
||||||
|
schema.ColumnTypeInt16,
|
||||||
|
schema.ColumnTypeBool,
|
||||||
|
schema.DateTime64ColumnType{Precision: 9, Timezone: "UTC"},
|
||||||
|
schema.FixedStringColumnType{Length: 32}:
|
||||||
|
return column.Name, nil
|
||||||
|
case schema.MapColumnType{
|
||||||
|
KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
|
||||||
|
ValueType: schema.ColumnTypeString,
|
||||||
|
}:
|
||||||
|
// a key could have been materialized, if so return the materialized column name
|
||||||
|
if key.Materialized {
|
||||||
|
return telemetrytypes.FieldKeyToMaterializedColumnName(key), nil
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s['%s']", column.Name, key.Name), nil
|
||||||
|
case schema.MapColumnType{
|
||||||
|
KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
|
||||||
|
ValueType: schema.ColumnTypeFloat64,
|
||||||
|
}:
|
||||||
|
// a key could have been materialized, if so return the materialized column name
|
||||||
|
if key.Materialized {
|
||||||
|
return telemetrytypes.FieldKeyToMaterializedColumnName(key), nil
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s['%s']", column.Name, key.Name), nil
|
||||||
|
case schema.MapColumnType{
|
||||||
|
KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
|
||||||
|
ValueType: schema.ColumnTypeBool,
|
||||||
|
}:
|
||||||
|
// a key could have been materialized, if so return the materialized column name
|
||||||
|
if key.Materialized {
|
||||||
|
return telemetrytypes.FieldKeyToMaterializedColumnName(key), nil
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s['%s']", column.Name, key.Name), nil
|
||||||
|
}
|
||||||
|
// should not reach here
|
||||||
|
return column.Name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conditionBuilder) GetCondition(
|
||||||
|
ctx context.Context,
|
||||||
|
key *telemetrytypes.TelemetryFieldKey,
|
||||||
|
operator qbtypes.FilterOperator,
|
||||||
|
value any,
|
||||||
|
sb *sqlbuilder.SelectBuilder,
|
||||||
|
) (string, error) {
|
||||||
|
column, err := c.GetColumn(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
tblFieldName, err := c.GetTableFieldName(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
tblFieldName, value = telemetrytypes.DataTypeCollisionHandledFieldName(key, value, tblFieldName)
|
||||||
|
|
||||||
|
// regular operators
|
||||||
|
switch operator {
|
||||||
|
// regular operators
|
||||||
|
case qbtypes.FilterOperatorEqual:
|
||||||
|
return sb.E(tblFieldName, value), nil
|
||||||
|
case qbtypes.FilterOperatorNotEqual:
|
||||||
|
return sb.NE(tblFieldName, value), nil
|
||||||
|
case qbtypes.FilterOperatorGreaterThan:
|
||||||
|
return sb.G(tblFieldName, value), nil
|
||||||
|
case qbtypes.FilterOperatorGreaterThanOrEq:
|
||||||
|
return sb.GE(tblFieldName, value), nil
|
||||||
|
case qbtypes.FilterOperatorLessThan:
|
||||||
|
return sb.LT(tblFieldName, value), nil
|
||||||
|
case qbtypes.FilterOperatorLessThanOrEq:
|
||||||
|
return sb.LE(tblFieldName, value), nil
|
||||||
|
|
||||||
|
// like and not like
|
||||||
|
case qbtypes.FilterOperatorLike:
|
||||||
|
return sb.Like(tblFieldName, value), nil
|
||||||
|
case qbtypes.FilterOperatorNotLike:
|
||||||
|
return sb.NotLike(tblFieldName, value), nil
|
||||||
|
case qbtypes.FilterOperatorILike:
|
||||||
|
return sb.ILike(tblFieldName, value), nil
|
||||||
|
case qbtypes.FilterOperatorNotILike:
|
||||||
|
return sb.NotILike(tblFieldName, value), nil
|
||||||
|
|
||||||
|
case qbtypes.FilterOperatorContains:
|
||||||
|
return sb.ILike(tblFieldName, value), nil
|
||||||
|
case qbtypes.FilterOperatorNotContains:
|
||||||
|
return sb.NotILike(tblFieldName, value), nil
|
||||||
|
|
||||||
|
case qbtypes.FilterOperatorRegexp:
|
||||||
|
exp := fmt.Sprintf(`match(%s, %s)`, tblFieldName, sb.Var(value))
|
||||||
|
return sb.And(exp), nil
|
||||||
|
case qbtypes.FilterOperatorNotRegexp:
|
||||||
|
exp := fmt.Sprintf(`not match(%s, %s)`, tblFieldName, sb.Var(value))
|
||||||
|
return sb.And(exp), nil
|
||||||
|
|
||||||
|
// between and not between
|
||||||
|
case qbtypes.FilterOperatorBetween:
|
||||||
|
values, ok := value.([]any)
|
||||||
|
if !ok {
|
||||||
|
return "", qbtypes.ErrBetweenValues
|
||||||
|
}
|
||||||
|
if len(values) != 2 {
|
||||||
|
return "", qbtypes.ErrBetweenValues
|
||||||
|
}
|
||||||
|
return sb.Between(tblFieldName, values[0], values[1]), nil
|
||||||
|
case qbtypes.FilterOperatorNotBetween:
|
||||||
|
values, ok := value.([]any)
|
||||||
|
if !ok {
|
||||||
|
return "", qbtypes.ErrBetweenValues
|
||||||
|
}
|
||||||
|
if len(values) != 2 {
|
||||||
|
return "", qbtypes.ErrBetweenValues
|
||||||
|
}
|
||||||
|
return sb.NotBetween(tblFieldName, values[0], values[1]), nil
|
||||||
|
|
||||||
|
// in and not in
|
||||||
|
case qbtypes.FilterOperatorIn:
|
||||||
|
values, ok := value.([]any)
|
||||||
|
if !ok {
|
||||||
|
return "", qbtypes.ErrInValues
|
||||||
|
}
|
||||||
|
return sb.In(tblFieldName, values...), nil
|
||||||
|
case qbtypes.FilterOperatorNotIn:
|
||||||
|
values, ok := value.([]any)
|
||||||
|
if !ok {
|
||||||
|
return "", qbtypes.ErrInValues
|
||||||
|
}
|
||||||
|
return sb.NotIn(tblFieldName, values...), nil
|
||||||
|
|
||||||
|
// exists and not exists
|
||||||
|
// in the query builder, `exists` and `not exists` are used for
|
||||||
|
// key membership checks, so depending on the column type, the condition changes
|
||||||
|
case qbtypes.FilterOperatorExists, qbtypes.FilterOperatorNotExists:
|
||||||
|
var value any
|
||||||
|
switch column.Type {
|
||||||
|
case schema.ColumnTypeString,
|
||||||
|
schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
|
||||||
|
schema.FixedStringColumnType{Length: 32},
|
||||||
|
schema.DateTime64ColumnType{Precision: 9, Timezone: "UTC"}:
|
||||||
|
value = ""
|
||||||
|
if operator == qbtypes.FilterOperatorExists {
|
||||||
|
return sb.NE(tblFieldName, value), nil
|
||||||
|
} else {
|
||||||
|
return sb.E(tblFieldName, value), nil
|
||||||
|
}
|
||||||
|
case schema.ColumnTypeUInt64,
|
||||||
|
schema.ColumnTypeUInt32,
|
||||||
|
schema.ColumnTypeUInt8,
|
||||||
|
schema.ColumnTypeInt8,
|
||||||
|
schema.ColumnTypeInt16,
|
||||||
|
schema.ColumnTypeBool:
|
||||||
|
value = 0
|
||||||
|
if operator == qbtypes.FilterOperatorExists {
|
||||||
|
return sb.NE(tblFieldName, value), nil
|
||||||
|
} else {
|
||||||
|
return sb.E(tblFieldName, value), nil
|
||||||
|
}
|
||||||
|
case schema.MapColumnType{
|
||||||
|
KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
|
||||||
|
ValueType: schema.ColumnTypeString,
|
||||||
|
}, schema.MapColumnType{
|
||||||
|
KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
|
||||||
|
ValueType: schema.ColumnTypeBool,
|
||||||
|
}, schema.MapColumnType{
|
||||||
|
KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
|
||||||
|
ValueType: schema.ColumnTypeFloat64,
|
||||||
|
}:
|
||||||
|
leftOperand := fmt.Sprintf("mapContains(%s, '%s')", column.Name, key.Name)
|
||||||
|
if key.Materialized {
|
||||||
|
leftOperand = telemetrytypes.FieldKeyToMaterializedColumnNameForExists(key)
|
||||||
|
}
|
||||||
|
if operator == qbtypes.FilterOperatorExists {
|
||||||
|
return sb.E(leftOperand, true), nil
|
||||||
|
} else {
|
||||||
|
return sb.NE(leftOperand, true), nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("exists operator is not supported for column type %s", column.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
298
pkg/telemetrytraces/condition_builder_test.go
Normal file
298
pkg/telemetrytraces/condition_builder_test.go
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
package telemetrytraces
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||||
|
"github.com/huandu/go-sqlbuilder"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetFieldKeyName(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
conditionBuilder := &conditionBuilder{}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
key telemetrytypes.TelemetryFieldKey
|
||||||
|
expectedResult string
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Simple column type - timestamp",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "timestamp",
|
||||||
|
FieldContext: telemetrytypes.FieldContextSpan,
|
||||||
|
},
|
||||||
|
expectedResult: "timestamp",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Map column type - string attribute",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "user.id",
|
||||||
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
|
},
|
||||||
|
expectedResult: "attributes_string['user.id']",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Map column type - number attribute",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "request.size",
|
||||||
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
|
FieldDataType: telemetrytypes.FieldDataTypeNumber,
|
||||||
|
},
|
||||||
|
expectedResult: "attributes_number['request.size']",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Map column type - bool attribute",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "request.success",
|
||||||
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
|
FieldDataType: telemetrytypes.FieldDataTypeBool,
|
||||||
|
},
|
||||||
|
expectedResult: "attributes_bool['request.success']",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Map column type - resource attribute",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "service.name",
|
||||||
|
FieldContext: telemetrytypes.FieldContextResource,
|
||||||
|
},
|
||||||
|
expectedResult: "resources_string['service.name']",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Non-existent column",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "nonexistent_field",
|
||||||
|
FieldContext: telemetrytypes.FieldContextSpan,
|
||||||
|
},
|
||||||
|
expectedResult: "",
|
||||||
|
expectedError: qbtypes.ErrColumnNotFound,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result, err := conditionBuilder.GetTableFieldName(ctx, &tc.key)
|
||||||
|
|
||||||
|
if tc.expectedError != nil {
|
||||||
|
assert.Equal(t, tc.expectedError, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tc.expectedResult, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetCondition(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
conditionBuilder := NewConditionBuilder()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
key telemetrytypes.TelemetryFieldKey
|
||||||
|
operator qbtypes.FilterOperator
|
||||||
|
value any
|
||||||
|
expectedSQL string
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Not Equal operator - timestamp",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "timestamp",
|
||||||
|
FieldContext: telemetrytypes.FieldContextSpan,
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorNotEqual,
|
||||||
|
value: uint64(1617979338000000000),
|
||||||
|
expectedSQL: "timestamp <> ?",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Greater Than operator - number attribute",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "request.duration",
|
||||||
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
|
FieldDataType: telemetrytypes.FieldDataTypeNumber,
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorGreaterThan,
|
||||||
|
value: float64(100),
|
||||||
|
expectedSQL: "attributes_number['request.duration'] > ?",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Less Than operator - number attribute",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "request.size",
|
||||||
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
|
FieldDataType: telemetrytypes.FieldDataTypeNumber,
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorLessThan,
|
||||||
|
value: float64(1024),
|
||||||
|
expectedSQL: "attributes_number['request.size'] < ?",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Greater Than Or Equal operator - timestamp",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "timestamp",
|
||||||
|
FieldContext: telemetrytypes.FieldContextSpan,
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorGreaterThanOrEq,
|
||||||
|
value: uint64(1617979338000000000),
|
||||||
|
expectedSQL: "timestamp >= ?",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Less Than Or Equal operator - timestamp",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "timestamp",
|
||||||
|
FieldContext: telemetrytypes.FieldContextSpan,
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorLessThanOrEq,
|
||||||
|
value: uint64(1617979338000000000),
|
||||||
|
expectedSQL: "timestamp <= ?",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ILike operator - string attribute",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "user.id",
|
||||||
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorILike,
|
||||||
|
value: "%admin%",
|
||||||
|
expectedSQL: "WHERE LOWER(attributes_string['user.id']) LIKE LOWER(?)",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Not ILike operator - string attribute",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "user.id",
|
||||||
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorNotILike,
|
||||||
|
value: "%admin%",
|
||||||
|
expectedSQL: "WHERE LOWER(attributes_string['user.id']) NOT LIKE LOWER(?)",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Between operator - timestamp",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "timestamp",
|
||||||
|
FieldContext: telemetrytypes.FieldContextSpan,
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorBetween,
|
||||||
|
value: []any{uint64(1617979338000000000), uint64(1617979348000000000)},
|
||||||
|
expectedSQL: "timestamp BETWEEN ? AND ?",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Between operator - invalid value",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "timestamp",
|
||||||
|
FieldContext: telemetrytypes.FieldContextSpan,
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorBetween,
|
||||||
|
value: "invalid",
|
||||||
|
expectedSQL: "",
|
||||||
|
expectedError: qbtypes.ErrBetweenValues,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Between operator - insufficient values",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "timestamp",
|
||||||
|
FieldContext: telemetrytypes.FieldContextSpan,
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorBetween,
|
||||||
|
value: []any{uint64(1617979338000000000)},
|
||||||
|
expectedSQL: "",
|
||||||
|
expectedError: qbtypes.ErrBetweenValues,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Not Between operator - timestamp",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "timestamp",
|
||||||
|
FieldContext: telemetrytypes.FieldContextSpan,
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorNotBetween,
|
||||||
|
value: []any{uint64(1617979338000000000), uint64(1617979348000000000)},
|
||||||
|
expectedSQL: "timestamp NOT BETWEEN ? AND ?",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Exists operator - map field",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "user.id",
|
||||||
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorExists,
|
||||||
|
value: nil,
|
||||||
|
expectedSQL: "mapContains(attributes_string, 'user.id') = ?",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Not Exists operator - map field",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "user.id",
|
||||||
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorNotExists,
|
||||||
|
value: nil,
|
||||||
|
expectedSQL: "mapContains(attributes_string, 'user.id') <> ?",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Contains operator - map field",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "user.id",
|
||||||
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||||
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorContains,
|
||||||
|
value: "admin",
|
||||||
|
expectedSQL: "WHERE LOWER(attributes_string['user.id']) LIKE LOWER(?)",
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Non-existent column",
|
||||||
|
key: telemetrytypes.TelemetryFieldKey{
|
||||||
|
Name: "nonexistent_field",
|
||||||
|
FieldContext: telemetrytypes.FieldContextSpan,
|
||||||
|
},
|
||||||
|
operator: qbtypes.FilterOperatorEqual,
|
||||||
|
value: "value",
|
||||||
|
expectedSQL: "",
|
||||||
|
expectedError: qbtypes.ErrColumnNotFound,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
sb := sqlbuilder.NewSelectBuilder()
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
cond, err := conditionBuilder.GetCondition(ctx, &tc.key, tc.operator, tc.value, sb)
|
||||||
|
sb.Where(cond)
|
||||||
|
|
||||||
|
if tc.expectedError != nil {
|
||||||
|
assert.Equal(t, tc.expectedError, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
sql, _ := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
|
||||||
|
assert.Contains(t, sql, tc.expectedSQL)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
10
pkg/telemetrytraces/tables.go
Normal file
10
pkg/telemetrytraces/tables.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package telemetrytraces
|
||||||
|
|
||||||
|
const (
|
||||||
|
DBName = "signoz_traces"
|
||||||
|
SpanIndexV3TableName = "distributed_signoz_index_v3"
|
||||||
|
SpanIndexV3LocalTableName = "signoz_index_v3"
|
||||||
|
TagAttributesV2TableName = "distributed_tag_attributes_v2"
|
||||||
|
TagAttributesV2LocalTableName = "tag_attributes_v2"
|
||||||
|
TopLevelOperationsTableName = "distributed_top_level_operations"
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user