package transition import ( "fmt" "strings" "time" "github.com/SigNoz/signoz/pkg/types/metrictypes" "github.com/SigNoz/signoz/pkg/types/telemetrytypes" "github.com/SigNoz/signoz/pkg/query-service/constants" v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3" "github.com/SigNoz/signoz/pkg/query-service/utils" v5 "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5" ) func ConvertV3ToV5(params *v3.QueryRangeParamsV3) (*v5.QueryRangeRequest, error) { v3Params := params.Clone() if v3Params == nil || v3Params.CompositeQuery == nil { return nil, fmt.Errorf("v3 params or composite query is nil") } varItems := map[string]v5.VariableItem{} for name, value := range v3Params.Variables { varItems[name] = v5.VariableItem{ Type: v5.QueryVariableType, // doesn't matter at the moment Value: value, } } v5Request := &v5.QueryRangeRequest{ SchemaVersion: "v5", Start: uint64(v3Params.Start), End: uint64(v3Params.End), RequestType: convertPanelTypeToRequestType(v3Params.CompositeQuery.PanelType), Variables: varItems, CompositeQuery: v5.CompositeQuery{ Queries: []v5.QueryEnvelope{}, }, FormatOptions: &v5.FormatOptions{ FormatTableResultForUI: v3Params.FormatForWeb, FillGaps: v3Params.CompositeQuery.FillGaps, }, } // Convert based on query type switch v3Params.CompositeQuery.QueryType { case v3.QueryTypeBuilder: if err := convertBuilderQueries(v3Params.CompositeQuery.BuilderQueries, &v5Request.CompositeQuery); err != nil { return nil, err } case v3.QueryTypeClickHouseSQL: if err := convertClickHouseQueries(v3Params.CompositeQuery.ClickHouseQueries, &v5Request.CompositeQuery); err != nil { return nil, err } case v3.QueryTypePromQL: if err := convertPromQueries(v3Params.CompositeQuery.PromQueries, v3Params.Step, &v5Request.CompositeQuery); err != nil { return nil, err } default: return nil, fmt.Errorf("unsupported query type: %s", v3Params.CompositeQuery.QueryType) } return v5Request, nil } func convertPanelTypeToRequestType(panelType v3.PanelType) v5.RequestType { switch panelType { case v3.PanelTypeValue, v3.PanelTypeTable: return v5.RequestTypeScalar case v3.PanelTypeGraph: return v5.RequestTypeTimeSeries case v3.PanelTypeList, v3.PanelTypeTrace: return v5.RequestTypeRaw default: return v5.RequestTypeUnknown } } func convertBuilderQueries(v3Queries map[string]*v3.BuilderQuery, v5Composite *v5.CompositeQuery) error { for name, query := range v3Queries { if query == nil { continue } // Handle formula queries if query.Expression != "" && query.Expression != name { v5Envelope := v5.QueryEnvelope{ Type: v5.QueryTypeFormula, Spec: v5.QueryBuilderFormula{ Name: name, Expression: query.Expression, Disabled: query.Disabled, Order: convertOrderBy(query.OrderBy, query), Limit: int(query.Limit), Having: convertHaving(query.Having, query), Functions: convertFunctions(query.Functions), }, } v5Composite.Queries = append(v5Composite.Queries, v5Envelope) continue } // Regular builder query envelope, err := convertSingleBuilderQuery(name, query) if err != nil { return err } v5Composite.Queries = append(v5Composite.Queries, envelope) } return nil } func convertSingleBuilderQuery(name string, v3Query *v3.BuilderQuery) (v5.QueryEnvelope, error) { v5Envelope := v5.QueryEnvelope{ Type: v5.QueryTypeBuilder, } switch v3Query.DataSource { case v3.DataSourceTraces: v5Query := v5.QueryBuilderQuery[v5.TraceAggregation]{ Name: name, Signal: telemetrytypes.SignalTraces, Disabled: v3Query.Disabled, StepInterval: v5.Step{Duration: time.Duration(v3Query.StepInterval) * time.Second}, Filter: convertFilter(v3Query.Filters), GroupBy: convertGroupBy(v3Query.GroupBy), Order: convertOrderBy(v3Query.OrderBy, v3Query), Limit: int(v3Query.Limit), Offset: int(v3Query.Offset), Having: convertHaving(v3Query.Having, v3Query), Functions: convertFunctions(v3Query.Functions), SelectFields: convertSelectColumns(v3Query.SelectColumns), } // Convert trace aggregations if v3Query.AggregateOperator != v3.AggregateOperatorNoOp { v5Query.Aggregations = []v5.TraceAggregation{ { Expression: buildTraceAggregationExpression(v3Query), Alias: "", }, } } v5Envelope.Spec = v5Query case v3.DataSourceLogs: v5Query := v5.QueryBuilderQuery[v5.LogAggregation]{ Name: name, Signal: telemetrytypes.SignalLogs, Disabled: v3Query.Disabled, StepInterval: v5.Step{Duration: time.Duration(v3Query.StepInterval) * time.Second}, Filter: convertFilter(v3Query.Filters), GroupBy: convertGroupBy(v3Query.GroupBy), Order: convertOrderBy(v3Query.OrderBy, v3Query), Limit: int(v3Query.PageSize), Offset: int(v3Query.Offset), Having: convertHaving(v3Query.Having, v3Query), Functions: convertFunctions(v3Query.Functions), } // Convert log aggregations if v3Query.AggregateOperator != v3.AggregateOperatorNoOp { v5Query.Aggregations = []v5.LogAggregation{ { Expression: buildLogAggregationExpression(v3Query), Alias: "", }, } } v5Envelope.Spec = v5Query case v3.DataSourceMetrics: v5Query := v5.QueryBuilderQuery[v5.MetricAggregation]{ Name: name, Signal: telemetrytypes.SignalMetrics, Disabled: v3Query.Disabled, StepInterval: v5.Step{Duration: time.Duration(v3Query.StepInterval) * time.Second}, Filter: convertFilter(v3Query.Filters), GroupBy: convertGroupBy(v3Query.GroupBy), Order: convertOrderBy(v3Query.OrderBy, v3Query), Limit: int(v3Query.Limit), Offset: int(v3Query.Offset), Having: convertHaving(v3Query.Having, v3Query), Functions: convertFunctions(v3Query.Functions), } if v3Query.AggregateAttribute.Key != "" { v5Query.Aggregations = []v5.MetricAggregation{ { MetricName: v3Query.AggregateAttribute.Key, Temporality: convertTemporality(v3Query.Temporality), TimeAggregation: convertTimeAggregation(v3Query.TimeAggregation), SpaceAggregation: convertSpaceAggregation(v3Query.SpaceAggregation), }, } } v5Envelope.Spec = v5Query default: return v5Envelope, fmt.Errorf("unsupported data source: %s", v3Query.DataSource) } return v5Envelope, nil } func buildTraceAggregationExpression(v3Query *v3.BuilderQuery) string { switch v3Query.AggregateOperator { case v3.AggregateOperatorCount: if v3Query.AggregateAttribute.Key != "" { return fmt.Sprintf("count(%s)", v3Query.AggregateAttribute.Key) } return "count()" case v3.AggregateOperatorCountDistinct: if v3Query.AggregateAttribute.Key != "" { return fmt.Sprintf("countDistinct(%s)", v3Query.AggregateAttribute.Key) } return "countDistinct()" case v3.AggregateOperatorSum: return fmt.Sprintf("sum(%s)", v3Query.AggregateAttribute.Key) case v3.AggregateOperatorAvg: return fmt.Sprintf("avg(%s)", v3Query.AggregateAttribute.Key) case v3.AggregateOperatorMin: return fmt.Sprintf("min(%s)", v3Query.AggregateAttribute.Key) case v3.AggregateOperatorMax: return fmt.Sprintf("max(%s)", v3Query.AggregateAttribute.Key) case v3.AggregateOperatorP05: return fmt.Sprintf("p05(%s)", v3Query.AggregateAttribute.Key) case v3.AggregateOperatorP10: return fmt.Sprintf("p10(%s)", v3Query.AggregateAttribute.Key) case v3.AggregateOperatorP20: return fmt.Sprintf("p20(%s)", v3Query.AggregateAttribute.Key) case v3.AggregateOperatorP25: return fmt.Sprintf("p25(%s)", v3Query.AggregateAttribute.Key) case v3.AggregateOperatorP50: return fmt.Sprintf("p50(%s)", v3Query.AggregateAttribute.Key) case v3.AggregateOperatorP75: return fmt.Sprintf("p75(%s)", v3Query.AggregateAttribute.Key) case v3.AggregateOperatorP90: return fmt.Sprintf("p90(%s)", v3Query.AggregateAttribute.Key) case v3.AggregateOperatorP95: return fmt.Sprintf("p95(%s)", v3Query.AggregateAttribute.Key) case v3.AggregateOperatorP99: return fmt.Sprintf("p99(%s)", v3Query.AggregateAttribute.Key) case v3.AggregateOperatorRate: return "rate()" case v3.AggregateOperatorRateSum: return fmt.Sprintf("rate_sum(%s)", v3Query.AggregateAttribute.Key) case v3.AggregateOperatorRateAvg: return fmt.Sprintf("rate_avg(%s)", v3Query.AggregateAttribute.Key) case v3.AggregateOperatorRateMin: return fmt.Sprintf("rate_min(%s)", v3Query.AggregateAttribute.Key) case v3.AggregateOperatorRateMax: return fmt.Sprintf("rate_max(%s)", v3Query.AggregateAttribute.Key) default: return "count()" } } func buildLogAggregationExpression(v3Query *v3.BuilderQuery) string { // Similar to traces return buildTraceAggregationExpression(v3Query) } func convertFilter(v3Filter *v3.FilterSet) *v5.Filter { if v3Filter == nil || len(v3Filter.Items) == 0 { return nil } expressions := []string{} for _, item := range v3Filter.Items { expr := buildFilterExpression(item) if expr != "" { expressions = append(expressions, expr) } } if len(expressions) == 0 { return nil } operator := "AND" if v3Filter.Operator == "OR" { operator = "OR" } return &v5.Filter{ Expression: strings.Join(expressions, fmt.Sprintf(" %s ", operator)), } } func buildFilterExpression(item v3.FilterItem) string { key := item.Key.Key value := item.Value switch item.Operator { case v3.FilterOperatorEqual: return fmt.Sprintf("%s = %s", key, formatValue(value)) case v3.FilterOperatorNotEqual: return fmt.Sprintf("%s != %s", key, formatValue(value)) case v3.FilterOperatorGreaterThan: return fmt.Sprintf("%s > %s", key, formatValue(value)) case v3.FilterOperatorGreaterThanOrEq: return fmt.Sprintf("%s >= %s", key, formatValue(value)) case v3.FilterOperatorLessThan: return fmt.Sprintf("%s < %s", key, formatValue(value)) case v3.FilterOperatorLessThanOrEq: return fmt.Sprintf("%s <= %s", key, formatValue(value)) case v3.FilterOperatorIn: return fmt.Sprintf("%s IN %s", key, formatValue(value)) case v3.FilterOperatorNotIn: return fmt.Sprintf("%s NOT IN %s", key, formatValue(value)) case v3.FilterOperatorContains: return fmt.Sprintf("%s LIKE '%%%v%%'", key, value) case v3.FilterOperatorNotContains: return fmt.Sprintf("%s NOT LIKE '%%%v%%'", key, value) case v3.FilterOperatorRegex: return fmt.Sprintf("%s REGEXP %s", key, formatValue(value)) case v3.FilterOperatorNotRegex: return fmt.Sprintf("%s NOT REGEXP %s", key, formatValue(value)) case v3.FilterOperatorExists: return fmt.Sprintf("%s EXISTS", key) case v3.FilterOperatorNotExists: return fmt.Sprintf("%s NOT EXISTS", key) default: return "" } } func formatValue(value interface{}) string { return utils.ClickHouseFormattedValue(value) } func convertGroupBy(v3GroupBy []v3.AttributeKey) []v5.GroupByKey { v5GroupBy := []v5.GroupByKey{} for _, key := range v3GroupBy { v5GroupBy = append(v5GroupBy, v5.GroupByKey{ TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{ Name: key.Key, FieldDataType: convertDataType(key.DataType), FieldContext: convertAttributeType(key.Type), Materialized: key.IsColumn, }, }) } return v5GroupBy } func convertOrderBy(v3OrderBy []v3.OrderBy, v3Query *v3.BuilderQuery) []v5.OrderBy { v5OrderBy := []v5.OrderBy{} for _, order := range v3OrderBy { direction := v5.OrderDirectionAsc if order.Order == v3.DirectionDesc { direction = v5.OrderDirectionDesc } var orderByName string if order.ColumnName == "#SIGNOZ_VALUE" { if v3Query.DataSource == v3.DataSourceLogs || v3Query.DataSource == v3.DataSourceTraces { orderByName = buildTraceAggregationExpression(v3Query) } else { if v3Query.Expression != v3Query.QueryName { orderByName = v3Query.Expression } else { orderByName = fmt.Sprintf("%s(%s)", v3Query.SpaceAggregation, v3Query.AggregateAttribute.Key) } } } else { orderByName = order.ColumnName } v5OrderBy = append(v5OrderBy, v5.OrderBy{ Key: v5.OrderByKey{ TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{ Name: orderByName, Materialized: order.IsColumn, }, }, Direction: direction, }) } return v5OrderBy } func convertHaving(v3Having []v3.Having, v3Query *v3.BuilderQuery) *v5.Having { if len(v3Having) == 0 { return nil } expressions := []string{} for _, h := range v3Having { var expr string if v3Query.DataSource == v3.DataSourceLogs || v3Query.DataSource == v3.DataSourceTraces { h.ColumnName = buildTraceAggregationExpression(v3Query) } else { if v3Query.Expression != v3Query.QueryName { h.ColumnName = v3Query.Expression } else { h.ColumnName = fmt.Sprintf("%s(%s)", v3Query.SpaceAggregation, v3Query.AggregateAttribute.Key) } } expr = buildHavingExpression(h) if expr != "" { expressions = append(expressions, expr) } } if len(expressions) == 0 { return nil } return &v5.Having{ Expression: strings.Join(expressions, " AND "), } } func buildHavingExpression(having v3.Having) string { switch having.Operator { case v3.HavingOperatorEqual: return fmt.Sprintf("%s = %s", having.ColumnName, formatValue(having.Value)) case v3.HavingOperatorNotEqual: return fmt.Sprintf("%s != %s", having.ColumnName, formatValue(having.Value)) case v3.HavingOperatorGreaterThan: return fmt.Sprintf("%s > %s", having.ColumnName, formatValue(having.Value)) case v3.HavingOperatorGreaterThanOrEq: return fmt.Sprintf("%s >= %s", having.ColumnName, formatValue(having.Value)) case v3.HavingOperatorLessThan: return fmt.Sprintf("%s < %s", having.ColumnName, formatValue(having.Value)) case v3.HavingOperatorLessThanOrEq: return fmt.Sprintf("%s <= %s", having.ColumnName, formatValue(having.Value)) case v3.HavingOperatorIn: return fmt.Sprintf("%s IN %s", having.ColumnName, formatValue(having.Value)) case v3.HavingOperatorNotIn: return fmt.Sprintf("%s NOT IN %s", having.ColumnName, formatValue(having.Value)) default: return "" } } func convertFunctions(v3Functions []v3.Function) []v5.Function { v5Functions := []v5.Function{} for _, fn := range v3Functions { v5Fn := v5.Function{ Name: convertFunctionName(fn.Name), Args: []v5.FunctionArg{}, } for _, arg := range fn.Args { v5Fn.Args = append(v5Fn.Args, v5.FunctionArg{ Value: arg, }) } for name, value := range fn.NamedArgs { v5Fn.Args = append(v5Fn.Args, v5.FunctionArg{ Name: name, Value: value, }) } v5Functions = append(v5Functions, v5Fn) } return v5Functions } func convertFunctionName(v3Name v3.FunctionName) v5.FunctionName { switch v3Name { case v3.FunctionNameCutOffMin: return v5.FunctionNameCutOffMin case v3.FunctionNameCutOffMax: return v5.FunctionNameCutOffMax case v3.FunctionNameClampMin: return v5.FunctionNameClampMin case v3.FunctionNameClampMax: return v5.FunctionNameClampMax case v3.FunctionNameAbsolute: return v5.FunctionNameAbsolute case v3.FunctionNameRunningDiff: return v5.FunctionNameRunningDiff case v3.FunctionNameLog2: return v5.FunctionNameLog2 case v3.FunctionNameLog10: return v5.FunctionNameLog10 case v3.FunctionNameCumSum: return v5.FunctionNameCumulativeSum case v3.FunctionNameEWMA3: return v5.FunctionNameEWMA3 case v3.FunctionNameEWMA5: return v5.FunctionNameEWMA5 case v3.FunctionNameEWMA7: return v5.FunctionNameEWMA7 case v3.FunctionNameMedian3: return v5.FunctionNameMedian3 case v3.FunctionNameMedian5: return v5.FunctionNameMedian5 case v3.FunctionNameMedian7: return v5.FunctionNameMedian7 case v3.FunctionNameTimeShift: return v5.FunctionNameTimeShift case v3.FunctionNameAnomaly: return v5.FunctionNameAnomaly default: return v5.FunctionName{} } } func convertSelectColumns(cols []v3.AttributeKey) []telemetrytypes.TelemetryFieldKey { fields := []telemetrytypes.TelemetryFieldKey{} for _, key := range cols { newKey := telemetrytypes.TelemetryFieldKey{ Name: key.Key, } if _, exists := constants.NewStaticFieldsTraces[key.Key]; exists { fields = append(fields, newKey) continue } if _, exists := constants.DeprecatedStaticFieldsTraces[key.Key]; exists { fields = append(fields, newKey) continue } if _, exists := constants.StaticFieldsLogsV3[key.Key]; exists { fields = append(fields, newKey) continue } newKey.FieldDataType = convertDataType(key.DataType) newKey.FieldContext = convertAttributeType(key.Type) newKey.Materialized = key.IsColumn } return fields } func convertDataType(v3Type v3.AttributeKeyDataType) telemetrytypes.FieldDataType { switch v3Type { case v3.AttributeKeyDataTypeString: return telemetrytypes.FieldDataTypeString case v3.AttributeKeyDataTypeInt64: return telemetrytypes.FieldDataTypeInt64 case v3.AttributeKeyDataTypeFloat64: return telemetrytypes.FieldDataTypeFloat64 case v3.AttributeKeyDataTypeBool: return telemetrytypes.FieldDataTypeBool case v3.AttributeKeyDataTypeArrayString: return telemetrytypes.FieldDataTypeArrayString case v3.AttributeKeyDataTypeArrayInt64: return telemetrytypes.FieldDataTypeArrayInt64 case v3.AttributeKeyDataTypeArrayFloat64: return telemetrytypes.FieldDataTypeArrayFloat64 case v3.AttributeKeyDataTypeArrayBool: return telemetrytypes.FieldDataTypeArrayBool default: return telemetrytypes.FieldDataTypeUnspecified } } func convertAttributeType(v3Type v3.AttributeKeyType) telemetrytypes.FieldContext { switch v3Type { case v3.AttributeKeyTypeTag: return telemetrytypes.FieldContextAttribute case v3.AttributeKeyTypeResource: return telemetrytypes.FieldContextResource case v3.AttributeKeyTypeInstrumentationScope: return telemetrytypes.FieldContextScope default: return telemetrytypes.FieldContextUnspecified } } func convertTemporality(v3Temp v3.Temporality) metrictypes.Temporality { switch v3Temp { case v3.Delta: return metrictypes.Delta case v3.Cumulative: return metrictypes.Cumulative default: return metrictypes.Unspecified } } func convertTimeAggregation(v3TimeAgg v3.TimeAggregation) metrictypes.TimeAggregation { switch v3TimeAgg { case v3.TimeAggregationAnyLast: return metrictypes.TimeAggregationLatest case v3.TimeAggregationSum: return metrictypes.TimeAggregationSum case v3.TimeAggregationAvg: return metrictypes.TimeAggregationAvg case v3.TimeAggregationMin: return metrictypes.TimeAggregationMin case v3.TimeAggregationMax: return metrictypes.TimeAggregationMax case v3.TimeAggregationCount: return metrictypes.TimeAggregationCount case v3.TimeAggregationCountDistinct: return metrictypes.TimeAggregationCountDistinct case v3.TimeAggregationRate: return metrictypes.TimeAggregationRate case v3.TimeAggregationIncrease: return metrictypes.TimeAggregationIncrease default: return metrictypes.TimeAggregationUnspecified } } func convertSpaceAggregation(v3SpaceAgg v3.SpaceAggregation) metrictypes.SpaceAggregation { switch v3SpaceAgg { case v3.SpaceAggregationSum: return metrictypes.SpaceAggregationSum case v3.SpaceAggregationAvg: return metrictypes.SpaceAggregationAvg case v3.SpaceAggregationMin: return metrictypes.SpaceAggregationMin case v3.SpaceAggregationMax: return metrictypes.SpaceAggregationMax case v3.SpaceAggregationCount: return metrictypes.SpaceAggregationCount case v3.SpaceAggregationPercentile50: return metrictypes.SpaceAggregationPercentile50 case v3.SpaceAggregationPercentile75: return metrictypes.SpaceAggregationPercentile75 case v3.SpaceAggregationPercentile90: return metrictypes.SpaceAggregationPercentile90 case v3.SpaceAggregationPercentile95: return metrictypes.SpaceAggregationPercentile95 case v3.SpaceAggregationPercentile99: return metrictypes.SpaceAggregationPercentile99 default: return metrictypes.SpaceAggregationUnspecified } } func convertClickHouseQueries(v3Queries map[string]*v3.ClickHouseQuery, v5Composite *v5.CompositeQuery) error { for name, query := range v3Queries { if query == nil { continue } v5Envelope := v5.QueryEnvelope{ Type: v5.QueryTypeClickHouseSQL, Spec: v5.ClickHouseQuery{ Name: name, Query: query.Query, Disabled: query.Disabled, }, } v5Composite.Queries = append(v5Composite.Queries, v5Envelope) } return nil } func convertPromQueries(v3Queries map[string]*v3.PromQuery, step int64, v5Composite *v5.CompositeQuery) error { for name, query := range v3Queries { if query == nil { continue } v5Envelope := v5.QueryEnvelope{ Type: v5.QueryTypePromQL, Spec: v5.PromQuery{ Name: name, Query: query.Query, Disabled: query.Disabled, Step: v5.Step{Duration: time.Duration(step) * time.Second}, Stats: query.Stats != "", }, } v5Composite.Queries = append(v5Composite.Queries, v5Envelope) } return nil }