From d0595bd7ede242ada56ac64ce905463a44f4c330 Mon Sep 17 00:00:00 2001 From: nityanandagohain Date: Fri, 8 Nov 2024 17:58:17 +0530 Subject: [PATCH 1/8] feat: trace V4 QB --- .../app/traces/v3/query_builder.go | 48 +- .../app/traces/v4/query_builder.go | 422 +++++++++++++ .../app/traces/v4/query_builder_test.go | 556 ++++++++++++++++++ pkg/query-service/constants/constants.go | 145 +++++ 4 files changed, 1147 insertions(+), 24 deletions(-) create mode 100644 pkg/query-service/app/traces/v4/query_builder.go create mode 100644 pkg/query-service/app/traces/v4/query_builder_test.go diff --git a/pkg/query-service/app/traces/v3/query_builder.go b/pkg/query-service/app/traces/v3/query_builder.go index ad5b69229b..f31088e509 100644 --- a/pkg/query-service/app/traces/v3/query_builder.go +++ b/pkg/query-service/app/traces/v3/query_builder.go @@ -10,7 +10,7 @@ import ( "go.signoz.io/signoz/pkg/query-service/utils" ) -var aggregateOperatorToPercentile = map[v3.AggregateOperator]float64{ +var AggregateOperatorToPercentile = map[v3.AggregateOperator]float64{ v3.AggregateOperatorP05: 0.05, v3.AggregateOperatorP10: 0.10, v3.AggregateOperatorP20: 0.20, @@ -22,7 +22,7 @@ var aggregateOperatorToPercentile = map[v3.AggregateOperator]float64{ v3.AggregateOperatorP99: 0.99, } -var aggregateOperatorToSQLFunc = map[v3.AggregateOperator]string{ +var AggregateOperatorToSQLFunc = map[v3.AggregateOperator]string{ v3.AggregateOperatorAvg: "avg", v3.AggregateOperatorMax: "max", v3.AggregateOperatorMin: "min", @@ -109,7 +109,7 @@ func getSelectLabels(aggregatorOperator v3.AggregateOperator, groupBy []v3.Attri return selectLabels } -func getSelectKeys(aggregatorOperator v3.AggregateOperator, groupBy []v3.AttributeKey) string { +func GetSelectKeys(aggregatorOperator v3.AggregateOperator, groupBy []v3.AttributeKey) string { var selectLabels []string if aggregatorOperator == v3.AggregateOperatorNoOp { return "" @@ -173,7 +173,7 @@ func buildTracesFilterQuery(fs *v3.FilterSet) (string, error) { conditions = append(conditions, fmt.Sprintf(operator, columnName, fmtVal)) case v3.FilterOperatorExists, v3.FilterOperatorNotExists: if item.Key.IsColumn { - subQuery, err := existsSubQueryForFixedColumn(item.Key, item.Operator) + subQuery, err := ExistsSubQueryForFixedColumn(item.Key, item.Operator) if err != nil { return "", err } @@ -199,7 +199,7 @@ func buildTracesFilterQuery(fs *v3.FilterSet) (string, error) { return queryString, nil } -func existsSubQueryForFixedColumn(key v3.AttributeKey, op v3.FilterOperator) (string, error) { +func ExistsSubQueryForFixedColumn(key v3.AttributeKey, op v3.FilterOperator) (string, error) { if key.DataType == v3.AttributeKeyDataTypeString { if op == v3.FilterOperatorExists { return fmt.Sprintf("%s %s ''", key.Key, tracesOperatorMappingV3[v3.FilterOperatorNotEqual]), nil @@ -244,7 +244,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, _ string, pan selectLabels := getSelectLabels(mq.AggregateOperator, mq.GroupBy) - having := having(mq.Having) + having := Having(mq.Having) if having != "" { having = " having " + having } @@ -272,7 +272,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, _ string, pan // we don't need value for first query if options.GraphLimitQtype == constants.FirstQueryGraphLimit { - queryTmpl = "SELECT " + getSelectKeys(mq.AggregateOperator, mq.GroupBy) + " from (" + queryTmpl + ")" + queryTmpl = "SELECT " + GetSelectKeys(mq.AggregateOperator, mq.GroupBy) + " from (" + queryTmpl + ")" } emptyValuesInGroupByFilter, err := handleEmptyValuesInGroupBy(mq.GroupBy) @@ -281,7 +281,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, _ string, pan } filterSubQuery += emptyValuesInGroupByFilter - groupBy := groupByAttributeKeyTags(panelType, options.GraphLimitQtype, mq.GroupBy...) + groupBy := GroupByAttributeKeyTags(panelType, options.GraphLimitQtype, mq.GroupBy...) if groupBy != "" { groupBy = " group by " + groupBy } @@ -291,7 +291,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, _ string, pan } if options.GraphLimitQtype == constants.SecondQueryGraphLimit { - filterSubQuery = filterSubQuery + " AND " + fmt.Sprintf("(%s) GLOBAL IN (", getSelectKeys(mq.AggregateOperator, mq.GroupBy)) + "%s)" + filterSubQuery = filterSubQuery + " AND " + fmt.Sprintf("(%s) GLOBAL IN (", GetSelectKeys(mq.AggregateOperator, mq.GroupBy)) + "%s)" } aggregationKey := "" @@ -311,7 +311,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, _ string, pan rate = rate / 60.0 } - op := fmt.Sprintf("%s(%s)/%f", aggregateOperatorToSQLFunc[mq.AggregateOperator], aggregationKey, rate) + op := fmt.Sprintf("%s(%s)/%f", AggregateOperatorToSQLFunc[mq.AggregateOperator], aggregationKey, rate) query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy) return query, nil case @@ -324,17 +324,17 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, _ string, pan v3.AggregateOperatorP90, v3.AggregateOperatorP95, v3.AggregateOperatorP99: - op := fmt.Sprintf("quantile(%v)(%s)", aggregateOperatorToPercentile[mq.AggregateOperator], aggregationKey) + op := fmt.Sprintf("quantile(%v)(%s)", AggregateOperatorToPercentile[mq.AggregateOperator], aggregationKey) query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy) return query, nil case v3.AggregateOperatorAvg, v3.AggregateOperatorSum, v3.AggregateOperatorMin, v3.AggregateOperatorMax: - op := fmt.Sprintf("%s(%s)", aggregateOperatorToSQLFunc[mq.AggregateOperator], aggregationKey) + op := fmt.Sprintf("%s(%s)", AggregateOperatorToSQLFunc[mq.AggregateOperator], aggregationKey) query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy) return query, nil case v3.AggregateOperatorCount: if mq.AggregateAttribute.Key != "" { if mq.AggregateAttribute.IsColumn { - subQuery, err := existsSubQueryForFixedColumn(mq.AggregateAttribute, v3.FilterOperatorExists) + subQuery, err := ExistsSubQueryForFixedColumn(mq.AggregateAttribute, v3.FilterOperatorExists) if err == nil { filterSubQuery = fmt.Sprintf("%s AND %s", filterSubQuery, subQuery) } @@ -354,9 +354,9 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, _ string, pan var query string if panelType == v3.PanelTypeTrace { withSubQuery := fmt.Sprintf(constants.TracesExplorerViewSQLSelectWithSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_LOCAL_TABLENAME, spanIndexTableTimeFilter, filterSubQuery) - withSubQuery = addLimitToQuery(withSubQuery, mq.Limit) + withSubQuery = AddLimitToQuery(withSubQuery, mq.Limit) if mq.Offset != 0 { - withSubQuery = addOffsetToQuery(withSubQuery, mq.Offset) + withSubQuery = AddOffsetToQuery(withSubQuery, mq.Offset) } // query = withSubQuery + ") " + fmt.Sprintf(constants.TracesExplorerViewSQLSelectQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_TABLENAME, constants.SIGNOZ_SPAN_INDEX_TABLENAME) query = fmt.Sprintf(constants.TracesExplorerViewSQLSelectBeforeSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_TABLENAME) + withSubQuery + ") " + fmt.Sprintf(constants.TracesExplorerViewSQLSelectAfterSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_TABLENAME, spanIndexTableTimeFilter) @@ -403,7 +403,7 @@ func groupBy(panelType v3.PanelType, graphLimitQtype string, tags ...string) str return strings.Join(tags, ",") } -func groupByAttributeKeyTags(panelType v3.PanelType, graphLimitQtype string, tags ...v3.AttributeKey) string { +func GroupByAttributeKeyTags(panelType v3.PanelType, graphLimitQtype string, tags ...v3.AttributeKey) string { groupTags := []string{} for _, tag := range tags { groupTags = append(groupTags, fmt.Sprintf("`%s`", tag.Key)) @@ -456,7 +456,7 @@ func orderByAttributeKeyTags(panelType v3.PanelType, items []v3.OrderBy, tags [] return str } -func having(items []v3.Having) string { +func Having(items []v3.Having) string { // aggregate something and filter on that aggregate var having []string for _, item := range items { @@ -465,7 +465,7 @@ func having(items []v3.Having) string { return strings.Join(having, " AND ") } -func reduceToQuery(query string, reduceTo v3.ReduceToOperator, _ v3.AggregateOperator) (string, error) { +func ReduceToQuery(query string, reduceTo v3.ReduceToOperator, _ v3.AggregateOperator) (string, error) { var groupBy string switch reduceTo { @@ -485,14 +485,14 @@ func reduceToQuery(query string, reduceTo v3.ReduceToOperator, _ v3.AggregateOpe return query, nil } -func addLimitToQuery(query string, limit uint64) string { +func AddLimitToQuery(query string, limit uint64) string { if limit == 0 { limit = 100 } return fmt.Sprintf("%s LIMIT %d", query, limit) } -func addOffsetToQuery(query string, offset uint64) string { +func AddOffsetToQuery(query string, offset uint64) string { return fmt.Sprintf("%s OFFSET %d", query, offset) } @@ -513,7 +513,7 @@ func PrepareTracesQuery(start, end int64, panelType v3.PanelType, mq *v3.Builder if err != nil { return "", err } - query = addLimitToQuery(query, mq.Limit) + query = AddLimitToQuery(query, mq.Limit) return query, nil } else if options.GraphLimitQtype == constants.SecondQueryGraphLimit { @@ -529,13 +529,13 @@ func PrepareTracesQuery(start, end int64, panelType v3.PanelType, mq *v3.Builder return "", err } if panelType == v3.PanelTypeValue { - query, err = reduceToQuery(query, mq.ReduceTo, mq.AggregateOperator) + query, err = ReduceToQuery(query, mq.ReduceTo, mq.AggregateOperator) } if panelType == v3.PanelTypeList || panelType == v3.PanelTypeTable { - query = addLimitToQuery(query, mq.Limit) + query = AddLimitToQuery(query, mq.Limit) if mq.Offset != 0 { - query = addOffsetToQuery(query, mq.Offset) + query = AddOffsetToQuery(query, mq.Offset) } } return query, err diff --git a/pkg/query-service/app/traces/v4/query_builder.go b/pkg/query-service/app/traces/v4/query_builder.go new file mode 100644 index 0000000000..ac9fff54e3 --- /dev/null +++ b/pkg/query-service/app/traces/v4/query_builder.go @@ -0,0 +1,422 @@ +package v4 + +import ( + "fmt" + "strings" + + "go.signoz.io/signoz/pkg/query-service/app/resource" + tracesV3 "go.signoz.io/signoz/pkg/query-service/app/traces/v3" + "go.signoz.io/signoz/pkg/query-service/constants" + v3 "go.signoz.io/signoz/pkg/query-service/model/v3" + "go.signoz.io/signoz/pkg/query-service/utils" +) + +const NANOSECOND = 1000000000 + +var tracesOperatorMappingV3 = map[v3.FilterOperator]string{ + v3.FilterOperatorIn: "IN", + v3.FilterOperatorNotIn: "NOT IN", + v3.FilterOperatorEqual: "=", + v3.FilterOperatorNotEqual: "!=", + v3.FilterOperatorLessThan: "<", + v3.FilterOperatorLessThanOrEq: "<=", + v3.FilterOperatorGreaterThan: ">", + v3.FilterOperatorGreaterThanOrEq: ">=", + v3.FilterOperatorLike: "ILIKE", + v3.FilterOperatorNotLike: "NOT ILIKE", + v3.FilterOperatorRegex: "match(%s, %s)", + v3.FilterOperatorNotRegex: "NOT match(%s, %s)", + v3.FilterOperatorContains: "ILIKE", + v3.FilterOperatorNotContains: "NOT ILIKE", + v3.FilterOperatorExists: "mapContains(%s, '%s')", + v3.FilterOperatorNotExists: "NOT has(%s%s, '%s')", +} + +func getClickHouseTracesColumnType(columnType v3.AttributeKeyType) string { + if columnType == v3.AttributeKeyTypeResource { + return "resources" + } + return "attributes" +} + +func getClickHouseTracesColumnDataType(columnDataType v3.AttributeKeyDataType) string { + if columnDataType == v3.AttributeKeyDataTypeFloat64 || columnDataType == v3.AttributeKeyDataTypeInt64 { + return "number" + } + if columnDataType == v3.AttributeKeyDataTypeBool { + return "bool" + } + return "string" +} + +func getColumnName(key v3.AttributeKey) string { + if !key.IsColumn { + keyType := getClickHouseTracesColumnType(key.Type) + keyDType := getClickHouseTracesColumnDataType(key.DataType) + return fmt.Sprintf("%s_%s['%s']", keyType, keyDType, key.Key) + } + + // check if it is a static field + if key.Type == v3.AttributeKeyTypeUnspecified { + // name is the column name + return key.Key + } + + // if key present in static return as it is + if _, ok := constants.StaticFieldsTraces[key.Key]; ok { + return key.Key + } + + return "`" + utils.GetClickhouseColumnNameV2(string(key.Type), string(key.DataType), key.Key) + "`" +} + +// getSelectLabels returns the select labels for the query based on groupBy and aggregateOperator +func getSelectLabels(groupBy []v3.AttributeKey) string { + var labels []string + for _, tag := range groupBy { + name := getColumnName(tag) + labels = append(labels, fmt.Sprintf(" %s as `%s`", name, tag.Key)) + } + return strings.Join(labels, ",") +} + +func buildTracesFilterQuery(fs *v3.FilterSet) (string, error) { + var conditions []string + + if fs != nil && len(fs.Items) != 0 { + for _, item := range fs.Items { + + // skip if it's a resource attribute + if item.Key.Type == v3.AttributeKeyTypeResource { + continue + } + + val := item.Value + // generate the key + columnName := getColumnName(item.Key) + var fmtVal string + item.Operator = v3.FilterOperator(strings.ToLower(strings.TrimSpace(string(item.Operator)))) + if item.Operator != v3.FilterOperatorExists && item.Operator != v3.FilterOperatorNotExists { + var err error + val, err = utils.ValidateAndCastValue(val, item.Key.DataType) + if err != nil { + return "", fmt.Errorf("invalid value for key %s: %v", item.Key.Key, err) + } + } + if val != nil { + fmtVal = utils.ClickHouseFormattedValue(val) + } + if operator, ok := tracesOperatorMappingV3[item.Operator]; ok { + switch item.Operator { + case v3.FilterOperatorContains, v3.FilterOperatorNotContains: + val = utils.QuoteEscapedString(fmt.Sprintf("%v", item.Value)) + conditions = append(conditions, fmt.Sprintf("%s %s '%%%s%%'", columnName, operator, val)) + case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex: + conditions = append(conditions, fmt.Sprintf(operator, columnName, fmtVal)) + case v3.FilterOperatorExists, v3.FilterOperatorNotExists: + if item.Key.IsColumn { + subQuery, err := tracesV3.ExistsSubQueryForFixedColumn(item.Key, item.Operator) + if err != nil { + return "", err + } + conditions = append(conditions, subQuery) + } else { + cType := getClickHouseTracesColumnType(item.Key.Type) + cDataType := getClickHouseTracesColumnDataType(item.Key.DataType) + col := fmt.Sprintf("%s_%s", cType, cDataType) + conditions = append(conditions, fmt.Sprintf(operator, col, item.Key.Key)) + } + + default: + conditions = append(conditions, fmt.Sprintf("%s %s %s", columnName, operator, fmtVal)) + } + } else { + return "", fmt.Errorf("unsupported operator %s", item.Operator) + } + } + } + queryString := strings.Join(conditions, " AND ") + + return queryString, nil +} + +func handleEmptyValuesInGroupBy(groupBy []v3.AttributeKey) (string, error) { + // TODO(nitya): in future when we support user based mat column handle them + // skipping now as we don't support creating them + filterItems := []v3.FilterItem{} + if len(groupBy) != 0 { + for _, item := range groupBy { + if !item.IsColumn { + filterItems = append(filterItems, v3.FilterItem{ + Key: item, + Operator: v3.FilterOperatorExists, + }) + } + } + } + if len(filterItems) != 0 { + filterSet := v3.FilterSet{ + Operator: "AND", + Items: filterItems, + } + return buildTracesFilterQuery(&filterSet) + } + return "", nil +} + +// orderBy returns a string of comma separated tags for order by clause +// if there are remaining items which are not present in tags they are also added +// if the order is not specified, it defaults to ASC +func orderBy(panelType v3.PanelType, items []v3.OrderBy, tagLookup map[string]struct{}) []string { + var orderBy []string + + for _, item := range items { + if item.ColumnName == constants.SigNozOrderByValue { + orderBy = append(orderBy, fmt.Sprintf("value %s", item.Order)) + } else if _, ok := tagLookup[item.ColumnName]; ok { + orderBy = append(orderBy, fmt.Sprintf("`%s` %s", item.ColumnName, item.Order)) + } else if panelType == v3.PanelTypeList { + attr := v3.AttributeKey{Key: item.ColumnName, DataType: item.DataType, Type: item.Type, IsColumn: item.IsColumn} + name := getColumnName(attr) + orderBy = append(orderBy, fmt.Sprintf("%s %s", name, item.Order)) + } + } + + return orderBy +} + +func orderByAttributeKeyTags(panelType v3.PanelType, items []v3.OrderBy, tags []v3.AttributeKey) string { + tagLookup := map[string]struct{}{} + for _, v := range tags { + tagLookup[v.Key] = struct{}{} + } + + orderByArray := orderBy(panelType, items, tagLookup) + + if len(orderByArray) == 0 { + if panelType == v3.PanelTypeList { + orderByArray = append(orderByArray, constants.TIMESTAMP+" DESC") + } else if panelType == v3.PanelTypeGraph { + orderByArray = append(orderByArray, "value DESC") + } + } + + str := strings.Join(orderByArray, ",") + return str +} + +func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, panelType v3.PanelType, options v3.QBOptions) (string, error) { + tracesStart := utils.GetEpochNanoSecs(start) + tracesEnd := utils.GetEpochNanoSecs(end) + + // -1800 this is added so that the bucket start considers all the fingerprints. + bucketStart := tracesStart/NANOSECOND - 1800 + bucketEnd := tracesEnd / NANOSECOND + + timeFilter := fmt.Sprintf("(timestamp >= '%d' AND timestamp <= '%d') AND (ts_bucket_start >= %d AND ts_bucket_start <= %d)", tracesStart, tracesEnd, bucketStart, bucketEnd) + + filterSubQuery, err := buildTracesFilterQuery(mq.Filters) + if err != nil { + return "", err + } + if filterSubQuery != "" { + filterSubQuery = " AND " + filterSubQuery + } + + emptyValuesInGroupByFilter, err := handleEmptyValuesInGroupBy(mq.GroupBy) + if err != nil { + return "", err + } + if emptyValuesInGroupByFilter != "" { + filterSubQuery = filterSubQuery + " AND " + emptyValuesInGroupByFilter + } + + resourceSubQuery, err := resource.BuildResourceSubQuery("signoz_traces", "distributed_traces_v3_resource", bucketStart, bucketEnd, mq.Filters, mq.GroupBy, mq.AggregateAttribute, false) + if err != nil { + return "", err + } + // join both the filter clauses + if resourceSubQuery != "" { + filterSubQuery = filterSubQuery + " AND (resource_fingerprint GLOBAL IN " + resourceSubQuery + ")" + } + + // timerange will be sent in epoch millisecond + selectLabels := getSelectLabels(mq.GroupBy) + if selectLabels != "" { + selectLabels = selectLabels + "," + } + + orderBy := orderByAttributeKeyTags(panelType, mq.OrderBy, mq.GroupBy) + if orderBy != "" { + orderBy = " order by " + orderBy + } + + if mq.AggregateOperator == v3.AggregateOperatorNoOp { + var query string + if panelType == v3.PanelTypeTrace { + withSubQuery := fmt.Sprintf(constants.TracesExplorerViewSQLSelectWithSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_V3_LOCAL_TABLENAME, timeFilter, filterSubQuery) + withSubQuery = tracesV3.AddLimitToQuery(withSubQuery, mq.Limit) + if mq.Offset != 0 { + withSubQuery = tracesV3.AddOffsetToQuery(withSubQuery, mq.Offset) + } + query = fmt.Sprintf(constants.TracesExplorerViewSQLSelectBeforeSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_V3) + withSubQuery + ") " + fmt.Sprintf(constants.TracesExplorerViewSQLSelectAfterSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_V3, timeFilter) + } else if panelType == v3.PanelTypeList { + if len(mq.SelectColumns) == 0 { + return "", fmt.Errorf("select columns cannot be empty for panelType %s", panelType) + } + // add it to the select labels + mq.SelectColumns = append(mq.SelectColumns, v3.AttributeKey{Key: "id", IsColumn: true}) + selectLabels = getSelectLabels(mq.SelectColumns) + queryNoOpTmpl := fmt.Sprintf("SELECT timestamp as timestamp_datetime, spanID, traceID,%s ", selectLabels) + "from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_V3 + " where %s %s" + "%s" + query = fmt.Sprintf(queryNoOpTmpl, timeFilter, filterSubQuery, orderBy) + } else { + return "", fmt.Errorf("unsupported aggregate operator %s for panelType %s", mq.AggregateOperator, panelType) + } + return query, nil + // ---- NOOP ends here ---- + } + + having := tracesV3.Having(mq.Having) + if having != "" { + having = " having " + having + } + + groupBy := tracesV3.GroupByAttributeKeyTags(panelType, options.GraphLimitQtype, mq.GroupBy...) + if groupBy != "" { + groupBy = " group by " + groupBy + } + + aggregationKey := "" + if mq.AggregateAttribute.Key != "" { + aggregationKey = getColumnName(mq.AggregateAttribute) + } + + var queryTmpl string + if options.GraphLimitQtype == constants.FirstQueryGraphLimit { + queryTmpl = "SELECT" + } else if panelType == v3.PanelTypeTable { + queryTmpl = + "SELECT " + // step or aggregate interval is whole time period in case of table panel + step = (tracesEnd - tracesStart) / 1000000000 + } else if panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue { + // Select the aggregate value for interval + queryTmpl = + fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d SECOND) AS ts,", step) + } + + queryTmpl = queryTmpl + selectLabels + + " %s as value " + + "from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_V3 + + " where " + timeFilter + "%s" + + "%s%s" + + "%s" + + // we don't need value for first query + if options.GraphLimitQtype == constants.FirstQueryGraphLimit { + queryTmpl = "SELECT " + tracesV3.GetSelectKeys(mq.AggregateOperator, mq.GroupBy) + " from (" + queryTmpl + ")" + } + + if options.GraphLimitQtype == constants.SecondQueryGraphLimit { + filterSubQuery = filterSubQuery + " AND " + fmt.Sprintf("(%s) GLOBAL IN (", tracesV3.GetSelectKeys(mq.AggregateOperator, mq.GroupBy)) + "%s)" + } + + switch mq.AggregateOperator { + case v3.AggregateOperatorRateSum, + v3.AggregateOperatorRateMax, + v3.AggregateOperatorRateAvg, + v3.AggregateOperatorRateMin, + v3.AggregateOperatorRate: + + rate := float64(step) + if options.PreferRPM { + rate = rate / 60.0 + } + + op := fmt.Sprintf("%s(%s)/%f", tracesV3.AggregateOperatorToSQLFunc[mq.AggregateOperator], aggregationKey, rate) + query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy) + return query, nil + case + v3.AggregateOperatorP05, + v3.AggregateOperatorP10, + v3.AggregateOperatorP20, + v3.AggregateOperatorP25, + v3.AggregateOperatorP50, + v3.AggregateOperatorP75, + v3.AggregateOperatorP90, + v3.AggregateOperatorP95, + v3.AggregateOperatorP99: + op := fmt.Sprintf("quantile(%v)(%s)", tracesV3.AggregateOperatorToPercentile[mq.AggregateOperator], aggregationKey) + query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy) + return query, nil + case v3.AggregateOperatorAvg, v3.AggregateOperatorSum, v3.AggregateOperatorMin, v3.AggregateOperatorMax: + op := fmt.Sprintf("%s(%s)", tracesV3.AggregateOperatorToSQLFunc[mq.AggregateOperator], aggregationKey) + query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy) + return query, nil + case v3.AggregateOperatorCount: + if mq.AggregateAttribute.Key != "" { + if mq.AggregateAttribute.IsColumn { + subQuery, err := tracesV3.ExistsSubQueryForFixedColumn(mq.AggregateAttribute, v3.FilterOperatorExists) + if err == nil { + filterSubQuery = fmt.Sprintf("%s AND %s", filterSubQuery, subQuery) + } + } else { + column := getColumnName(mq.AggregateAttribute) + filterSubQuery = fmt.Sprintf("%s AND has(%s, '%s')", filterSubQuery, column, mq.AggregateAttribute.Key) + } + } + op := "toFloat64(count())" + query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy) + return query, nil + case v3.AggregateOperatorCountDistinct: + op := fmt.Sprintf("toFloat64(count(distinct(%s)))", aggregationKey) + query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy) + return query, nil + default: + return "", fmt.Errorf("unsupported aggregate operator %s", mq.AggregateOperator) + } +} + +// PrepareTracesQuery returns the query string for traces +// start and end are in epoch millisecond +// step is in seconds +func PrepareTracesQuery(start, end int64, panelType v3.PanelType, mq *v3.BuilderQuery, options v3.QBOptions) (string, error) { + // adjust the start and end time to the step interval + if panelType == v3.PanelTypeGraph { + // adjust the start and end time to the step interval for graph panel types + start = start - (start % (mq.StepInterval * 1000)) + end = end - (end % (mq.StepInterval * 1000)) + } + if options.GraphLimitQtype == constants.FirstQueryGraphLimit { + // give me just the group by names + query, err := buildTracesQuery(start, end, mq.StepInterval, mq, panelType, options) + if err != nil { + return "", err + } + query = tracesV3.AddLimitToQuery(query, mq.Limit) + + return query, nil + } else if options.GraphLimitQtype == constants.SecondQueryGraphLimit { + query, err := buildTracesQuery(start, end, mq.StepInterval, mq, panelType, options) + if err != nil { + return "", err + } + return query, nil + } + + query, err := buildTracesQuery(start, end, mq.StepInterval, mq, panelType, options) + if err != nil { + return "", err + } + if panelType == v3.PanelTypeValue { + query, err = tracesV3.ReduceToQuery(query, mq.ReduceTo, mq.AggregateOperator) + } + if panelType == v3.PanelTypeList || panelType == v3.PanelTypeTable { + query = tracesV3.AddLimitToQuery(query, mq.Limit) + + if mq.Offset != 0 { + query = tracesV3.AddOffsetToQuery(query, mq.Offset) + } + } + return query, err +} diff --git a/pkg/query-service/app/traces/v4/query_builder_test.go b/pkg/query-service/app/traces/v4/query_builder_test.go new file mode 100644 index 0000000000..3a5a07b9aa --- /dev/null +++ b/pkg/query-service/app/traces/v4/query_builder_test.go @@ -0,0 +1,556 @@ +package v4 + +import ( + "testing" + + "go.signoz.io/signoz/pkg/query-service/constants" + v3 "go.signoz.io/signoz/pkg/query-service/model/v3" +) + +func Test_getClickHouseTracesColumnType(t *testing.T) { + type args struct { + columnType v3.AttributeKeyType + } + tests := []struct { + name string + args args + want string + }{ + { + name: "tag", + args: args{ + columnType: v3.AttributeKeyTypeTag, + }, + want: "attributes", + }, + { + name: "resource", + args: args{ + columnType: v3.AttributeKeyTypeResource, + }, + want: "resources", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getClickHouseTracesColumnType(tt.args.columnType); got != tt.want { + t.Errorf("GetClickhouseTracesColumnType() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getClickHouseTracesColumnDataType(t *testing.T) { + type args struct { + columnDataType v3.AttributeKeyDataType + } + tests := []struct { + name string + args args + want string + }{ + { + name: "string", + args: args{ + columnDataType: v3.AttributeKeyDataTypeString, + }, + want: "string", + }, + { + name: "float64", + args: args{ + columnDataType: v3.AttributeKeyDataTypeFloat64, + }, + want: "number", + }, + { + name: "int64", + args: args{ + columnDataType: v3.AttributeKeyDataTypeInt64, + }, + want: "number", + }, + { + name: "bool", + args: args{ + columnDataType: v3.AttributeKeyDataTypeBool, + }, + want: "bool", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getClickHouseTracesColumnDataType(tt.args.columnDataType); got != tt.want { + t.Errorf("getClickhouseTracesColumnDataType() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getColumnName(t *testing.T) { + type args struct { + key v3.AttributeKey + } + tests := []struct { + name string + args args + want string + }{ + { + name: "tag", + args: args{ + key: v3.AttributeKey{Key: "data", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + }, + want: "attributes_string['data']", + }, + { + name: "column", + args: args{ + key: v3.AttributeKey{Key: "data", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, + }, + want: "`attribute_string_data`", + }, + { + name: "static column", + args: args{ + key: v3.AttributeKey{Key: "spanKind", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, + }, + want: "spanKind", + }, + { + name: "missing meta", + args: args{ + key: v3.AttributeKey{Key: "xyz"}, + }, + want: "attributes_string['xyz']", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getColumnName(tt.args.key); got != tt.want { + t.Errorf("getColumnName() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getSelectLabels(t *testing.T) { + type args struct { + groupBy []v3.AttributeKey + } + tests := []struct { + name string + args args + want string + }{ + { + name: "count", + args: args{ + groupBy: []v3.AttributeKey{{Key: "user_name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, + }, + want: " attributes_string['user_name'] as `user_name`", + }, + { + name: "multiple group by", + args: args{ + groupBy: []v3.AttributeKey{ + {Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, // static col + {Key: "service_name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource, IsColumn: true}, + }, + }, + want: " name as `name`, `resource_string_service_name` as `service_name`", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getSelectLabels(tt.args.groupBy); got != tt.want { + t.Errorf("getSelectLabels() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_buildTracesFilterQuery(t *testing.T) { + type args struct { + fs *v3.FilterSet + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "Test buildTracesFilterQuery in, nin", + args: args{ + fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ + {Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: []interface{}{"GET", "POST"}, Operator: v3.FilterOperatorIn}, + {Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: []interface{}{"PUT"}, Operator: v3.FilterOperatorNotIn}, + {Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource}, Value: []interface{}{"server"}, Operator: v3.FilterOperatorNotIn}, + }}, + }, + want: "attributes_string['method'] IN ['GET','POST'] AND attributes_string['method'] NOT IN ['PUT']", + wantErr: false, + }, + { + name: "Test buildTracesFilterQuery not eq, neq, gt, lt, gte, lte", + args: args{ + fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ + {Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: 102, Operator: v3.FilterOperatorEqual}, + {Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: 100, Operator: v3.FilterOperatorNotEqual}, + {Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: 10, Operator: v3.FilterOperatorGreaterThan}, + {Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: 200, Operator: v3.FilterOperatorLessThan}, + {Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: 10, Operator: v3.FilterOperatorGreaterThanOrEq}, + {Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: 200, Operator: v3.FilterOperatorLessThanOrEq}, + }}, + }, + want: "attributes_number['duration'] = 102 AND attributes_number['duration'] != 100 AND attributes_number['duration'] > 10 AND attributes_number['duration'] < 200" + + " AND attributes_number['duration'] >= 10 AND attributes_number['duration'] <= 200", + wantErr: false, + }, + { + name: "Test contains, ncontains, like, nlike, regex, nregex", + args: args{ + fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ + {Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "102.", Operator: v3.FilterOperatorContains}, + {Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "103", Operator: v3.FilterOperatorNotContains}, + {Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "102.", Operator: v3.FilterOperatorLike}, + {Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "102", Operator: v3.FilterOperatorNotLike}, + {Key: v3.AttributeKey{Key: "path", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, Value: "/mypath", Operator: v3.FilterOperatorRegex}, + {Key: v3.AttributeKey{Key: "path", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, Value: "/health.*", Operator: v3.FilterOperatorNotRegex}, + }}, + }, + want: "attributes_string['host'] ILIKE '%102.%' AND attributes_string['host'] NOT ILIKE '%103%' AND attributes_string['host'] ILIKE '102.' AND attributes_string['host'] NOT ILIKE '102' AND " + + "match(`attribute_string_path`, '/mypath') AND NOT match(`attribute_string_path`, '/health.*')", + }, + { + name: "Test exists, nexists", + args: args{ + fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ + {Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Operator: v3.FilterOperatorExists}, + {Key: v3.AttributeKey{Key: "path", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, Operator: v3.FilterOperatorNotExists}, + }}, + }, + want: "mapContains(attributes_string, 'host') AND path = ''", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := buildTracesFilterQuery(tt.args.fs) + if (err != nil) != tt.wantErr { + t.Errorf("buildTracesFilterQuery() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("buildTracesFilterQuery() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_handleEmptyValuesInGroupBy(t *testing.T) { + type args struct { + groupBy []v3.AttributeKey + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "Test handleEmptyValuesInGroupBy", + args: args{ + groupBy: []v3.AttributeKey{{Key: "bytes", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, + }, + want: "mapContains(attributes_string, 'bytes')", + wantErr: false, + }, + { + name: "Test handleEmptyValuesInGroupBy", + args: args{ + groupBy: []v3.AttributeKey{{Key: "bytes", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}}, + }, + want: "", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := handleEmptyValuesInGroupBy(tt.args.groupBy) + if (err != nil) != tt.wantErr { + t.Errorf("handleEmptyValuesInGroupBy() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("handleEmptyValuesInGroupBy() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_orderByAttributeKeyTags(t *testing.T) { + type args struct { + panelType v3.PanelType + items []v3.OrderBy + tags []v3.AttributeKey + } + tests := []struct { + name string + args args + want string + }{ + { + name: "test", + args: args{ + panelType: v3.PanelTypeGraph, + items: []v3.OrderBy{{ColumnName: "name", Order: "ASC"}}, + tags: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, + }, + want: "`name` ASC", + }, + { + name: "order by value", + args: args{ + panelType: v3.PanelTypeGraph, + items: []v3.OrderBy{{ColumnName: "name", Order: "ASC"}, {ColumnName: constants.SigNozOrderByValue, Order: "DESC"}}, + tags: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, + }, + want: "`name` ASC,value DESC", + }, + { + name: "test", + args: args{ + panelType: v3.PanelTypeList, + items: []v3.OrderBy{{ColumnName: "status", Order: "DESC", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + {ColumnName: "route", Order: "DESC", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}}, + }, + want: "attributes_string['status'] DESC,`attribute_string_route` DESC", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := orderByAttributeKeyTags(tt.args.panelType, tt.args.items, tt.args.tags); got != tt.want { + t.Errorf("orderByAttributeKeyTags() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_buildTracesQuery(t *testing.T) { + type args struct { + start int64 + end int64 + step int64 + mq *v3.BuilderQuery + panelType v3.PanelType + options v3.QBOptions + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "Test buildTracesQuery", + args: args{ + panelType: v3.PanelTypeTable, + start: 1680066360726210000, + end: 1680066458000000000, + step: 1000, + mq: &v3.BuilderQuery{ + AggregateOperator: v3.AggregateOperatorCount, + Filters: &v3.FilterSet{ + Items: []v3.FilterItem{ + { + Key: v3.AttributeKey{Key: "http.method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + Value: 100, + Operator: v3.FilterOperatorEqual, + }, + }, + }, + GroupBy: []v3.AttributeKey{{Key: "http.method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, + OrderBy: []v3.OrderBy{ + {ColumnName: "http.method", Order: "ASC"}}, + }, + }, + want: "SELECT attributes_string['http.method'] as `http.method`, toFloat64(count()) as value from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + + "AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND attributes_string['http.method'] = '100' AND mapContains(attributes_string, 'http.method') " + + "group by `http.method` order by `http.method` ASC", + }, + { + name: "Test buildTracesQuery", + args: args{ + panelType: v3.PanelTypeTable, + start: 1680066360726210000, + end: 1680066458000000000, + step: 1000, + mq: &v3.BuilderQuery{ + AggregateOperator: v3.AggregateOperatorCount, + Filters: &v3.FilterSet{ + Items: []v3.FilterItem{ + {Key: v3.AttributeKey{Key: "bytes", Type: v3.AttributeKeyTypeTag, DataType: v3.AttributeKeyDataTypeInt64}, Value: 100, Operator: ">"}, + {Key: v3.AttributeKey{Key: "service.name", Type: v3.AttributeKeyTypeResource, DataType: v3.AttributeKeyDataTypeString}, Value: "myService", Operator: "="}, + }, + }, + GroupBy: []v3.AttributeKey{{Key: "host", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeResource}}, + OrderBy: []v3.OrderBy{ + {ColumnName: "host", Order: "ASC"}}, + }, + }, + want: "SELECT resources_number['host'] as `host`, toFloat64(count()) as value from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + + "AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND attributes_number['bytes'] > 100 AND " + + "(resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (seen_at_ts_bucket_start >= 1680064560) AND " + + "(seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'service.name') = 'myService' AND labels like '%service.name%myService%' AND " + + "( (simpleJSONHas(labels, 'host') AND labels like '%host%') ))) " + + "group by `host` order by `host` ASC", + }, + { + name: "test noop list view", + args: args{ + panelType: v3.PanelTypeList, + start: 1680066360726210000, + end: 1680066458000000000, + mq: &v3.BuilderQuery{ + AggregateOperator: v3.AggregateOperatorNoOp, + Filters: &v3.FilterSet{}, + SelectColumns: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}}, + }, + }, + want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name`, id as `id` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + + "AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) order by timestamp DESC", + }, + { + name: "test noop trace view", + args: args{ + panelType: v3.PanelTypeTrace, + start: 1680066360726210000, + end: 1680066458000000000, + mq: &v3.BuilderQuery{ + AggregateOperator: v3.AggregateOperatorNoOp, + Filters: &v3.FilterSet{ + Items: []v3.FilterItem{ + {Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "GET", Operator: "="}, + {Key: v3.AttributeKey{Key: "service.name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource}, Value: "myService", Operator: "="}, + }, + }, + }, + }, + want: "SELECT subQuery.serviceName, subQuery.name, count() AS span_count, subQuery.durationNano, subQuery.traceID AS traceID FROM signoz_traces.distributed_signoz_index_v3 INNER JOIN " + + "( SELECT * FROM (SELECT traceID, durationNano, serviceName, name FROM signoz_traces.signoz_index_v3 WHERE parentSpanID = '' AND (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND " + + "(ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND attributes_string['method'] = 'GET' AND (resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource " + + "WHERE (seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'service.name') = 'myService' AND labels like '%service.name%myService%')) " + + "ORDER BY durationNano DESC LIMIT 1 BY traceID LIMIT 100) AS inner_subquery ) AS subQuery ON signoz_traces.distributed_signoz_index_v3.traceID = subQuery.traceID WHERE (timestamp >= '1680066360726210000' AND " + + "timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) GROUP BY subQuery.traceID, subQuery.durationNano, subQuery.name, subQuery.serviceName ORDER BY " + + "subQuery.durationNano desc LIMIT 1 BY subQuery.traceID;", + }, + { + name: "Test order by value with having", + args: args{ + panelType: v3.PanelTypeTable, + start: 1680066360726210000, + end: 1680066458000000000, + mq: &v3.BuilderQuery{ + AggregateOperator: v3.AggregateOperatorCountDistinct, + Filters: &v3.FilterSet{}, + AggregateAttribute: v3.AttributeKey{Key: "name", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + OrderBy: []v3.OrderBy{{ColumnName: "#SIGNOZ_VALUE", Order: "ASC"}}, + Having: []v3.Having{ + { + ColumnName: "name", + Operator: ">", + Value: 10, + }, + }, + }, + }, + want: "SELECT toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND " + + "(ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) having value > 10 order by value ASC", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := buildTracesQuery(tt.args.start, tt.args.end, tt.args.step, tt.args.mq, tt.args.panelType, tt.args.options) + if (err != nil) != tt.wantErr { + t.Errorf("buildTracesQuery() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("buildTracesQuery() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPrepareTracesQuery(t *testing.T) { + type args struct { + start int64 + end int64 + panelType v3.PanelType + mq *v3.BuilderQuery + options v3.QBOptions + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "test with limit - first", + args: args{ + start: 1680066360726210000, + end: 1680066458000000000, + panelType: v3.PanelTypeTable, + mq: &v3.BuilderQuery{ + StepInterval: 60, + AggregateOperator: v3.AggregateOperatorCountDistinct, + Filters: &v3.FilterSet{}, + AggregateAttribute: v3.AttributeKey{Key: "name", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + GroupBy: []v3.AttributeKey{{Key: "serviceName", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, + Limit: 10, + }, + options: v3.QBOptions{ + GraphLimitQtype: constants.FirstQueryGraphLimit, + }, + }, + want: "SELECT `serviceName` from (SELECT serviceName as `serviceName`, toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 " + + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) group by `serviceName`) LIMIT 10", + }, + { + name: "test with limit - second", + args: args{ + start: 1680066360726210000, + end: 1680066458000000000, + panelType: v3.PanelTypeTable, + mq: &v3.BuilderQuery{ + StepInterval: 60, + AggregateOperator: v3.AggregateOperatorCountDistinct, + Filters: &v3.FilterSet{}, + AggregateAttribute: v3.AttributeKey{Key: "name", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + GroupBy: []v3.AttributeKey{{Key: "serviceName", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, + Limit: 10, + }, + options: v3.QBOptions{ + GraphLimitQtype: constants.SecondQueryGraphLimit, + }, + }, + want: "SELECT serviceName as `serviceName`, toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 where " + + "(timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND (`serviceName`) GLOBAL IN (%s) group by `serviceName`", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := PrepareTracesQuery(tt.args.start, tt.args.end, tt.args.panelType, tt.args.mq, tt.args.options) + if (err != nil) != tt.wantErr { + t.Errorf("PrepareTracesQuery() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("PrepareTracesQuery() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/query-service/constants/constants.go b/pkg/query-service/constants/constants.go index b1a12880ed..aa9b59062f 100644 --- a/pkg/query-service/constants/constants.go +++ b/pkg/query-service/constants/constants.go @@ -239,6 +239,8 @@ const ( SIGNOZ_TRACE_DBNAME = "signoz_traces" SIGNOZ_SPAN_INDEX_TABLENAME = "distributed_signoz_index_v2" SIGNOZ_SPAN_INDEX_LOCAL_TABLENAME = "signoz_index_v2" + SIGNOZ_SPAN_INDEX_V3 = "distributed_signoz_index_v3" + SIGNOZ_SPAN_INDEX_V3_LOCAL_TABLENAME = "signoz_index_v3" SIGNOZ_TIMESERIES_v4_LOCAL_TABLENAME = "time_series_v4" SIGNOZ_TIMESERIES_v4_6HRS_LOCAL_TABLENAME = "time_series_v4_6hrs" SIGNOZ_TIMESERIES_v4_1DAY_LOCAL_TABLENAME = "time_series_v4_1day" @@ -444,3 +446,146 @@ const MaxFilterSuggestionsExamplesLimit = 10 var SpanRenderLimitStr = GetOrDefaultEnv("SPAN_RENDER_LIMIT", "2500") var MaxSpansInTraceStr = GetOrDefaultEnv("MAX_SPANS_IN_TRACE", "250000") + +var StaticFieldsTraces = map[string]v3.AttributeKey{ + "traceID": { + Key: "traceID", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "spanID": { + Key: "spanID", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "parentSpanID": { + Key: "parentSpanID", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "name": { + Key: "name", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "serviceName": { + Key: "serviceName", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "kind": { + Key: "kind", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "spanKind": { + Key: "spanKind", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "durationNano": { + Key: "durationNano", + DataType: v3.AttributeKeyDataTypeFloat64, + IsColumn: true, + }, + "statusCode": { + Key: "statusCode", + DataType: v3.AttributeKeyDataTypeFloat64, + IsColumn: true, + }, + "hasError": { + Key: "hasError", + DataType: v3.AttributeKeyDataTypeBool, + IsColumn: true, + }, + "statusMessage": { + Key: "statusMessage", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "statusCodeString": { + Key: "statusCodeString", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "externalHttpMethod": { + Key: "externalHttpMethod", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "externalHttpUrl": { + Key: "externalHttpUrl", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "dbSystem": { + Key: "dbSystem", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "dbName": { + Key: "dbName", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "dbOperation": { + Key: "dbOperation", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "peerService": { + Key: "peerService", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "httpMethod": { + Key: "httpMethod", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "httpUrl": { + Key: "httpUrl", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "httpRoute": { + Key: "httpRoute", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "httpHost": { + Key: "httpHost", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "msgSystem": { + Key: "msgSystem", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "msgOperation": { + Key: "msgOperation", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "rpcSystem": { + Key: "rpcSystem", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "rpcService": { + Key: "rpcService", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "rpcMethod": { + Key: "rpcMethod", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, + "responseStatusCode": { + Key: "responseStatusCode", + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, + }, +} From 4637dc189997204477702898ab847ff36ec009ae Mon Sep 17 00:00:00 2001 From: nityanandagohain Date: Wed, 13 Nov 2024 14:44:37 +0530 Subject: [PATCH 2/8] fix: update get column name and remove id --- pkg/query-service/app/traces/v4/query_builder.go | 7 ------- pkg/query-service/app/traces/v4/query_builder_test.go | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/pkg/query-service/app/traces/v4/query_builder.go b/pkg/query-service/app/traces/v4/query_builder.go index ac9fff54e3..f4403e59d9 100644 --- a/pkg/query-service/app/traces/v4/query_builder.go +++ b/pkg/query-service/app/traces/v4/query_builder.go @@ -56,12 +56,6 @@ func getColumnName(key v3.AttributeKey) string { return fmt.Sprintf("%s_%s['%s']", keyType, keyDType, key.Key) } - // check if it is a static field - if key.Type == v3.AttributeKeyTypeUnspecified { - // name is the column name - return key.Key - } - // if key present in static return as it is if _, ok := constants.StaticFieldsTraces[key.Key]; ok { return key.Key @@ -265,7 +259,6 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, panelType v3. return "", fmt.Errorf("select columns cannot be empty for panelType %s", panelType) } // add it to the select labels - mq.SelectColumns = append(mq.SelectColumns, v3.AttributeKey{Key: "id", IsColumn: true}) selectLabels = getSelectLabels(mq.SelectColumns) queryNoOpTmpl := fmt.Sprintf("SELECT timestamp as timestamp_datetime, spanID, traceID,%s ", selectLabels) + "from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_V3 + " where %s %s" + "%s" query = fmt.Sprintf(queryNoOpTmpl, timeFilter, filterSubQuery, orderBy) diff --git a/pkg/query-service/app/traces/v4/query_builder_test.go b/pkg/query-service/app/traces/v4/query_builder_test.go index 3a5a07b9aa..fab4c29afe 100644 --- a/pkg/query-service/app/traces/v4/query_builder_test.go +++ b/pkg/query-service/app/traces/v4/query_builder_test.go @@ -418,7 +418,7 @@ func Test_buildTracesQuery(t *testing.T) { SelectColumns: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}}, }, }, - want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name`, id as `id` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + + want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) order by timestamp DESC", }, { From e8705b36975a748bb666c9764460be8a345e5afa Mon Sep 17 00:00:00 2001 From: nityanandagohain Date: Wed, 13 Nov 2024 17:18:23 +0530 Subject: [PATCH 3/8] fix: handle contains and update tests --- .../app/traces/v4/query_builder.go | 5 ++- .../app/traces/v4/query_builder_test.go | 37 ++++++++++++++----- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/pkg/query-service/app/traces/v4/query_builder.go b/pkg/query-service/app/traces/v4/query_builder.go index f4403e59d9..ac45543b3a 100644 --- a/pkg/query-service/app/traces/v4/query_builder.go +++ b/pkg/query-service/app/traces/v4/query_builder.go @@ -29,7 +29,7 @@ var tracesOperatorMappingV3 = map[v3.FilterOperator]string{ v3.FilterOperatorContains: "ILIKE", v3.FilterOperatorNotContains: "NOT ILIKE", v3.FilterOperatorExists: "mapContains(%s, '%s')", - v3.FilterOperatorNotExists: "NOT has(%s%s, '%s')", + v3.FilterOperatorNotExists: "NOT mapContains(%s, '%s')", } func getClickHouseTracesColumnType(columnType v3.AttributeKeyType) string { @@ -103,7 +103,8 @@ func buildTracesFilterQuery(fs *v3.FilterSet) (string, error) { if operator, ok := tracesOperatorMappingV3[item.Operator]; ok { switch item.Operator { case v3.FilterOperatorContains, v3.FilterOperatorNotContains: - val = utils.QuoteEscapedString(fmt.Sprintf("%v", item.Value)) + // we also want to treat %, _ as literals for contains + val := utils.QuoteEscapedStringForContains(fmt.Sprintf("%s", item.Value), false) conditions = append(conditions, fmt.Sprintf("%s %s '%%%s%%'", columnName, operator, val)) case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex: conditions = append(conditions, fmt.Sprintf(operator, columnName, fmtVal)) diff --git a/pkg/query-service/app/traces/v4/query_builder_test.go b/pkg/query-service/app/traces/v4/query_builder_test.go index fab4c29afe..12b098341b 100644 --- a/pkg/query-service/app/traces/v4/query_builder_test.go +++ b/pkg/query-service/app/traces/v4/query_builder_test.go @@ -180,6 +180,15 @@ func Test_buildTracesFilterQuery(t *testing.T) { want string wantErr bool }{ + { + name: "Test ignore resource", + args: args{ + fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ + {Key: v3.AttributeKey{Key: "service.name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource}, Value: []interface{}{"service"}, Operator: v3.FilterOperatorIn}, + }, + }}, + want: "", + }, { name: "Test buildTracesFilterQuery in, nin", args: args{ @@ -187,9 +196,12 @@ func Test_buildTracesFilterQuery(t *testing.T) { {Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: []interface{}{"GET", "POST"}, Operator: v3.FilterOperatorIn}, {Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: []interface{}{"PUT"}, Operator: v3.FilterOperatorNotIn}, {Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource}, Value: []interface{}{"server"}, Operator: v3.FilterOperatorNotIn}, + {Key: v3.AttributeKey{Key: "status.code", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: []interface{}{200}, Operator: v3.FilterOperatorNotIn}, + {Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeFloat64, Type: v3.AttributeKeyTypeTag}, Value: []interface{}{100.0}, Operator: v3.FilterOperatorIn}, + {Key: v3.AttributeKey{Key: "isDone", DataType: v3.AttributeKeyDataTypeBool, Type: v3.AttributeKeyTypeTag}, Value: []interface{}{true}, Operator: v3.FilterOperatorIn}, }}, }, - want: "attributes_string['method'] IN ['GET','POST'] AND attributes_string['method'] NOT IN ['PUT']", + want: "attributes_string['method'] IN ['GET','POST'] AND attributes_string['method'] NOT IN ['PUT'] AND attributes_number['status.code'] NOT IN [200] AND attributes_number['duration'] IN [100] AND attributes_bool['isDone'] IN [true]", wantErr: false, }, { @@ -200,27 +212,27 @@ func Test_buildTracesFilterQuery(t *testing.T) { {Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: 100, Operator: v3.FilterOperatorNotEqual}, {Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: 10, Operator: v3.FilterOperatorGreaterThan}, {Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: 200, Operator: v3.FilterOperatorLessThan}, - {Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: 10, Operator: v3.FilterOperatorGreaterThanOrEq}, - {Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: 200, Operator: v3.FilterOperatorLessThanOrEq}, + {Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeFloat64, Type: v3.AttributeKeyTypeTag}, Value: 10.0, Operator: v3.FilterOperatorGreaterThanOrEq}, + {Key: v3.AttributeKey{Key: "duration_str", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "200", Operator: v3.FilterOperatorLessThanOrEq}, }}, }, want: "attributes_number['duration'] = 102 AND attributes_number['duration'] != 100 AND attributes_number['duration'] > 10 AND attributes_number['duration'] < 200" + - " AND attributes_number['duration'] >= 10 AND attributes_number['duration'] <= 200", + " AND attributes_number['duration'] >= 10.000000 AND attributes_string['duration_str'] <= '200'", wantErr: false, }, { name: "Test contains, ncontains, like, nlike, regex, nregex", args: args{ fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ - {Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "102.", Operator: v3.FilterOperatorContains}, - {Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "103", Operator: v3.FilterOperatorNotContains}, + {Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "102.%", Operator: v3.FilterOperatorContains}, + {Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "103_", Operator: v3.FilterOperatorNotContains}, {Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "102.", Operator: v3.FilterOperatorLike}, {Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "102", Operator: v3.FilterOperatorNotLike}, {Key: v3.AttributeKey{Key: "path", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, Value: "/mypath", Operator: v3.FilterOperatorRegex}, {Key: v3.AttributeKey{Key: "path", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, Value: "/health.*", Operator: v3.FilterOperatorNotRegex}, }}, }, - want: "attributes_string['host'] ILIKE '%102.%' AND attributes_string['host'] NOT ILIKE '%103%' AND attributes_string['host'] ILIKE '102.' AND attributes_string['host'] NOT ILIKE '102' AND " + + want: "attributes_string['host'] ILIKE '%102.\\%%' AND attributes_string['host'] NOT ILIKE '%103\\_%' AND attributes_string['host'] ILIKE '102.' AND attributes_string['host'] NOT ILIKE '102' AND " + "match(`attribute_string_path`, '/mypath') AND NOT match(`attribute_string_path`, '/health.*')", }, { @@ -228,10 +240,13 @@ func Test_buildTracesFilterQuery(t *testing.T) { args: args{ fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ {Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Operator: v3.FilterOperatorExists}, + {Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Operator: v3.FilterOperatorExists}, + {Key: v3.AttributeKey{Key: "isDone", DataType: v3.AttributeKeyDataTypeBool, Type: v3.AttributeKeyTypeTag}, Operator: v3.FilterOperatorNotExists}, + {Key: v3.AttributeKey{Key: "host1", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Operator: v3.FilterOperatorNotExists}, {Key: v3.AttributeKey{Key: "path", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, Operator: v3.FilterOperatorNotExists}, }}, }, - want: "mapContains(attributes_string, 'host') AND path = ''", + want: "mapContains(attributes_string, 'host') AND mapContains(attributes_number, 'duration') AND NOT mapContains(attributes_bool, 'isDone') AND NOT mapContains(attributes_string, 'host1') AND path = ''", }, } for _, tt := range tests { @@ -510,13 +525,14 @@ func TestPrepareTracesQuery(t *testing.T) { AggregateAttribute: v3.AttributeKey{Key: "name", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, GroupBy: []v3.AttributeKey{{Key: "serviceName", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, Limit: 10, + OrderBy: []v3.OrderBy{{ColumnName: "#SIGNOZ_VALUE", Order: "DESC"}}, }, options: v3.QBOptions{ GraphLimitQtype: constants.FirstQueryGraphLimit, }, }, want: "SELECT `serviceName` from (SELECT serviceName as `serviceName`, toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 " + - "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) group by `serviceName`) LIMIT 10", + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) group by `serviceName` order by value DESC) LIMIT 10", }, { name: "test with limit - second", @@ -530,6 +546,7 @@ func TestPrepareTracesQuery(t *testing.T) { Filters: &v3.FilterSet{}, AggregateAttribute: v3.AttributeKey{Key: "name", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, GroupBy: []v3.AttributeKey{{Key: "serviceName", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, + OrderBy: []v3.OrderBy{{ColumnName: "#SIGNOZ_VALUE", Order: "DESC"}}, Limit: 10, }, options: v3.QBOptions{ @@ -537,7 +554,7 @@ func TestPrepareTracesQuery(t *testing.T) { }, }, want: "SELECT serviceName as `serviceName`, toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 where " + - "(timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND (`serviceName`) GLOBAL IN (%s) group by `serviceName`", + "(timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND (`serviceName`) GLOBAL IN (%s) group by `serviceName` order by value DESC", }, } From fd9dd527a4d26d21f53eb3fb4853770fafd4b7e6 Mon Sep 17 00:00:00 2001 From: nityanandagohain Date: Wed, 13 Nov 2024 17:26:33 +0530 Subject: [PATCH 4/8] fix: remove unwanted step interval calculation --- pkg/query-service/app/logs/v4/query_builder.go | 2 -- pkg/query-service/app/traces/v4/query_builder.go | 2 -- 2 files changed, 4 deletions(-) diff --git a/pkg/query-service/app/logs/v4/query_builder.go b/pkg/query-service/app/logs/v4/query_builder.go index 42cb19befc..a808a3b57d 100644 --- a/pkg/query-service/app/logs/v4/query_builder.go +++ b/pkg/query-service/app/logs/v4/query_builder.go @@ -436,8 +436,6 @@ func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.Build } else if panelType == v3.PanelTypeTable { queryTmplPrefix = "SELECT" - // step or aggregate interval is whole time period in case of table panel - step = (utils.GetEpochNanoSecs(end) - utils.GetEpochNanoSecs(start)) / NANOSECOND } else if panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue { // Select the aggregate value for interval queryTmplPrefix = diff --git a/pkg/query-service/app/traces/v4/query_builder.go b/pkg/query-service/app/traces/v4/query_builder.go index ac45543b3a..9d8c8fe791 100644 --- a/pkg/query-service/app/traces/v4/query_builder.go +++ b/pkg/query-service/app/traces/v4/query_builder.go @@ -291,8 +291,6 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, panelType v3. } else if panelType == v3.PanelTypeTable { queryTmpl = "SELECT " - // step or aggregate interval is whole time period in case of table panel - step = (tracesEnd - tracesStart) / 1000000000 } else if panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue { // Select the aggregate value for interval queryTmpl = From 789aa0dd5795708acb8765993e964a66897997f9 Mon Sep 17 00:00:00 2001 From: nityanandagohain Date: Wed, 13 Nov 2024 17:45:01 +0530 Subject: [PATCH 5/8] fix: add test cases --- .../app/traces/v4/query_builder.go | 10 +- .../app/traces/v4/query_builder_test.go | 115 ++++++++++++++++-- pkg/query-service/constants/constants.go | 1 + 3 files changed, 113 insertions(+), 13 deletions(-) diff --git a/pkg/query-service/app/traces/v4/query_builder.go b/pkg/query-service/app/traces/v4/query_builder.go index 9d8c8fe791..824921b7e8 100644 --- a/pkg/query-service/app/traces/v4/query_builder.go +++ b/pkg/query-service/app/traces/v4/query_builder.go @@ -50,17 +50,17 @@ func getClickHouseTracesColumnDataType(columnDataType v3.AttributeKeyDataType) s } func getColumnName(key v3.AttributeKey) string { + // if key present in static return as it is + if _, ok := constants.StaticFieldsTraces[key.Key]; ok { + return key.Key + } + if !key.IsColumn { keyType := getClickHouseTracesColumnType(key.Type) keyDType := getClickHouseTracesColumnDataType(key.DataType) return fmt.Sprintf("%s_%s['%s']", keyType, keyDType, key.Key) } - // if key present in static return as it is - if _, ok := constants.StaticFieldsTraces[key.Key]; ok { - return key.Key - } - return "`" + utils.GetClickhouseColumnNameV2(string(key.Type), string(key.DataType), key.Key) + "`" } diff --git a/pkg/query-service/app/traces/v4/query_builder_test.go b/pkg/query-service/app/traces/v4/query_builder_test.go index 12b098341b..449fefc039 100644 --- a/pkg/query-service/app/traces/v4/query_builder_test.go +++ b/pkg/query-service/app/traces/v4/query_builder_test.go @@ -423,6 +423,22 @@ func Test_buildTracesQuery(t *testing.T) { }, { name: "test noop list view", + args: args{ + panelType: v3.PanelTypeList, + start: 1680066360726210000, + end: 1680066458000000000, + mq: &v3.BuilderQuery{ + AggregateOperator: v3.AggregateOperatorNoOp, + Filters: &v3.FilterSet{}, + SelectColumns: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}}, + OrderBy: []v3.OrderBy{{ColumnName: "timestamp", Order: "ASC"}}, + }, + }, + want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + + "AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) order by timestamp ASC", + }, + { + name: "test noop list view-without ts", args: args{ panelType: v3.PanelTypeList, start: 1680066360726210000, @@ -522,8 +538,8 @@ func TestPrepareTracesQuery(t *testing.T) { StepInterval: 60, AggregateOperator: v3.AggregateOperatorCountDistinct, Filters: &v3.FilterSet{}, - AggregateAttribute: v3.AttributeKey{Key: "name", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, - GroupBy: []v3.AttributeKey{{Key: "serviceName", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, + AggregateAttribute: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + GroupBy: []v3.AttributeKey{{Key: "function", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, Limit: 10, OrderBy: []v3.OrderBy{{ColumnName: "#SIGNOZ_VALUE", Order: "DESC"}}, }, @@ -531,8 +547,8 @@ func TestPrepareTracesQuery(t *testing.T) { GraphLimitQtype: constants.FirstQueryGraphLimit, }, }, - want: "SELECT `serviceName` from (SELECT serviceName as `serviceName`, toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 " + - "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) group by `serviceName` order by value DESC) LIMIT 10", + want: "SELECT `function` from (SELECT attributes_string['function'] as `function`, toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 " + + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND mapContains(attributes_string, 'function') group by `function` order by value DESC) LIMIT 10", }, { name: "test with limit - second", @@ -544,8 +560,8 @@ func TestPrepareTracesQuery(t *testing.T) { StepInterval: 60, AggregateOperator: v3.AggregateOperatorCountDistinct, Filters: &v3.FilterSet{}, - AggregateAttribute: v3.AttributeKey{Key: "name", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, - GroupBy: []v3.AttributeKey{{Key: "serviceName", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, + AggregateAttribute: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + GroupBy: []v3.AttributeKey{{Key: "function", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, OrderBy: []v3.OrderBy{{ColumnName: "#SIGNOZ_VALUE", Order: "DESC"}}, Limit: 10, }, @@ -553,8 +569,91 @@ func TestPrepareTracesQuery(t *testing.T) { GraphLimitQtype: constants.SecondQueryGraphLimit, }, }, - want: "SELECT serviceName as `serviceName`, toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 where " + - "(timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND (`serviceName`) GLOBAL IN (%s) group by `serviceName` order by value DESC", + want: "SELECT attributes_string['function'] as `function`, toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 where " + + "(timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND mapContains(attributes_string, 'function') AND (`function`) GLOBAL IN (%s) group by `function` order by value DESC", + }, + { + name: "test with limit with resources- first", + args: args{ + start: 1680066360726210000, + end: 1680066458000000000, + panelType: v3.PanelTypeTable, + mq: &v3.BuilderQuery{ + StepInterval: 60, + AggregateOperator: v3.AggregateOperatorCountDistinct, + Filters: &v3.FilterSet{ + Operator: "AND", + Items: []v3.FilterItem{ + { + Key: v3.AttributeKey{Key: "line", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, + Value: 100, + Operator: v3.FilterOperatorEqual, + }, + { + Key: v3.AttributeKey{Key: "hostname", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource}, + Value: "server1", + Operator: v3.FilterOperatorEqual, + }, + }, + }, + AggregateAttribute: v3.AttributeKey{Key: "name", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + GroupBy: []v3.AttributeKey{ + {Key: "function", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + {Key: "service.name", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource}, + }, + Limit: 10, + OrderBy: []v3.OrderBy{{ColumnName: "#SIGNOZ_VALUE", Order: "DESC"}}, + }, + options: v3.QBOptions{ + GraphLimitQtype: constants.FirstQueryGraphLimit, + }, + }, + want: "SELECT `function`,`service.name` from (SELECT `attribute_string_function` as `function`, `resource_string_service$$name` as `service.name`, toFloat64(count(distinct(name))) as value " + + "from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) " + + "AND attributes_number['line'] = 100 AND (resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE " + + "(seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'hostname') = 'server1' AND labels like '%hostname%server1%' AND " + + "( (simpleJSONHas(labels, 'service.name') AND labels like '%service.name%') ))) group by `function`,`service.name` order by value DESC) LIMIT 10", + }, + { + name: "test with limit with resources - second", + args: args{ + start: 1680066360726210000, + end: 1680066458000000000, + panelType: v3.PanelTypeTable, + mq: &v3.BuilderQuery{ + StepInterval: 60, + AggregateOperator: v3.AggregateOperatorCountDistinct, + Filters: &v3.FilterSet{ + Operator: "AND", + Items: []v3.FilterItem{ + { + Key: v3.AttributeKey{Key: "line", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, + Value: 100, + Operator: v3.FilterOperatorEqual, + }, + { + Key: v3.AttributeKey{Key: "hostname", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource}, + Value: "server1", + Operator: v3.FilterOperatorEqual, + }, + }, + }, + AggregateAttribute: v3.AttributeKey{Key: "name", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + GroupBy: []v3.AttributeKey{ + {Key: "function", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + {Key: "serviceName", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + }, + OrderBy: []v3.OrderBy{{ColumnName: "#SIGNOZ_VALUE", Order: "DESC"}}, + Limit: 10, + }, + options: v3.QBOptions{ + GraphLimitQtype: constants.SecondQueryGraphLimit, + }, + }, + want: "SELECT `attribute_string_function` as `function`, serviceName as `serviceName`, toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 " + + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND attributes_number['line'] = 100 " + + "AND (resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) " + + "AND simpleJSONExtractString(labels, 'hostname') = 'server1' AND labels like '%hostname%server1%')) AND (`function`,`serviceName`) GLOBAL IN (%s) group by `function`,`serviceName` order by value DESC", }, } diff --git a/pkg/query-service/constants/constants.go b/pkg/query-service/constants/constants.go index aa9b59062f..dc52f6fd88 100644 --- a/pkg/query-service/constants/constants.go +++ b/pkg/query-service/constants/constants.go @@ -448,6 +448,7 @@ var SpanRenderLimitStr = GetOrDefaultEnv("SPAN_RENDER_LIMIT", "2500") var MaxSpansInTraceStr = GetOrDefaultEnv("MAX_SPANS_IN_TRACE", "250000") var StaticFieldsTraces = map[string]v3.AttributeKey{ + "timestamp": {}, "traceID": { Key: "traceID", DataType: v3.AttributeKeyDataTypeString, From 0dc76d17bf689688f4ca1f8b3a014bae82f231fb Mon Sep 17 00:00:00 2001 From: nityanandagohain Date: Wed, 13 Nov 2024 18:07:50 +0530 Subject: [PATCH 6/8] fix: add tests for static columns in QB --- .../app/traces/v4/query_builder_test.go | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pkg/query-service/app/traces/v4/query_builder_test.go b/pkg/query-service/app/traces/v4/query_builder_test.go index 449fefc039..c5c8c886b5 100644 --- a/pkg/query-service/app/traces/v4/query_builder_test.go +++ b/pkg/query-service/app/traces/v4/query_builder_test.go @@ -499,6 +499,30 @@ func Test_buildTracesQuery(t *testing.T) { want: "SELECT toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND " + "(ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) having value > 10 order by value ASC", }, + { + name: "test buildTraceQuery with static columns", + args: args{ + panelType: v3.PanelTypeTable, + start: 1680066360726210000, + end: 1680066458000000000, + mq: &v3.BuilderQuery{ + AggregateOperator: v3.AggregateOperatorCount, + Filters: &v3.FilterSet{ + Items: []v3.FilterItem{ + {Key: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, IsColumn: true}, Value: "GET", Operator: "="}, + {Key: v3.AttributeKey{Key: "spanID", DataType: v3.AttributeKeyDataTypeString, IsColumn: true}, Value: "myService", Operator: "="}, + }, + }, + OrderBy: []v3.OrderBy{{ColumnName: "#SIGNOZ_VALUE", Order: "ASC"}}, + GroupBy: []v3.AttributeKey{ + {Key: "serviceName", DataType: v3.AttributeKeyDataTypeString, IsColumn: true}, + }, + }, + }, + want: "SELECT serviceName as `serviceName`, toFloat64(count()) as value from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND " + + "(ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND name = 'GET' AND spanID = 'myService' group by `serviceName` order by value ASC", + }, + // TODO (nitya): add test for the new column names that are added } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 097a54b84f332fdc1f5a74bee52c72be19f0c482 Mon Sep 17 00:00:00 2001 From: nityanandagohain Date: Wed, 13 Nov 2024 20:07:16 +0530 Subject: [PATCH 7/8] fix: add more order by tests --- .../app/traces/v4/query_builder_test.go | 60 +++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/pkg/query-service/app/traces/v4/query_builder_test.go b/pkg/query-service/app/traces/v4/query_builder_test.go index c5c8c886b5..59fe1998ef 100644 --- a/pkg/query-service/app/traces/v4/query_builder_test.go +++ b/pkg/query-service/app/traces/v4/query_builder_test.go @@ -342,6 +342,42 @@ func Test_orderByAttributeKeyTags(t *testing.T) { }, want: "attributes_string['status'] DESC,`attribute_string_route` DESC", }, + { + name: "ignore order by in table panel", + args: args{ + panelType: v3.PanelTypeTable, + items: []v3.OrderBy{{ColumnName: "timestamp", Order: "DESC"}}, + tags: []v3.AttributeKey{}, + }, + want: "", + }, + { + name: "add default order by ts for list panel", + args: args{ + panelType: v3.PanelTypeList, + items: []v3.OrderBy{}, + tags: []v3.AttributeKey{}, + }, + want: "timestamp DESC", + }, + { + name: "add default order by value for graph panel", + args: args{ + panelType: v3.PanelTypeGraph, + items: []v3.OrderBy{}, + tags: []v3.AttributeKey{}, + }, + want: "value DESC", + }, + { + name: "don't add default order by for table panel", + args: args{ + panelType: v3.PanelTypeTable, + items: []v3.OrderBy{}, + tags: []v3.AttributeKey{}, + }, + want: "", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -499,30 +535,6 @@ func Test_buildTracesQuery(t *testing.T) { want: "SELECT toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND " + "(ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) having value > 10 order by value ASC", }, - { - name: "test buildTraceQuery with static columns", - args: args{ - panelType: v3.PanelTypeTable, - start: 1680066360726210000, - end: 1680066458000000000, - mq: &v3.BuilderQuery{ - AggregateOperator: v3.AggregateOperatorCount, - Filters: &v3.FilterSet{ - Items: []v3.FilterItem{ - {Key: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, IsColumn: true}, Value: "GET", Operator: "="}, - {Key: v3.AttributeKey{Key: "spanID", DataType: v3.AttributeKeyDataTypeString, IsColumn: true}, Value: "myService", Operator: "="}, - }, - }, - OrderBy: []v3.OrderBy{{ColumnName: "#SIGNOZ_VALUE", Order: "ASC"}}, - GroupBy: []v3.AttributeKey{ - {Key: "serviceName", DataType: v3.AttributeKeyDataTypeString, IsColumn: true}, - }, - }, - }, - want: "SELECT serviceName as `serviceName`, toFloat64(count()) as value from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND " + - "(ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND name = 'GET' AND spanID = 'myService' group by `serviceName` order by value ASC", - }, - // TODO (nitya): add test for the new column names that are added } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 2698097ee8e82d268170edb42e5bac5195718ff6 Mon Sep 17 00:00:00 2001 From: nityanandagohain Date: Wed, 13 Nov 2024 20:13:52 +0530 Subject: [PATCH 8/8] fix: update order by logic --- pkg/query-service/app/traces/v4/query_builder.go | 2 +- pkg/query-service/app/traces/v4/query_builder_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/query-service/app/traces/v4/query_builder.go b/pkg/query-service/app/traces/v4/query_builder.go index 824921b7e8..4a0f8f3ef9 100644 --- a/pkg/query-service/app/traces/v4/query_builder.go +++ b/pkg/query-service/app/traces/v4/query_builder.go @@ -191,7 +191,7 @@ func orderByAttributeKeyTags(panelType v3.PanelType, items []v3.OrderBy, tags [] if len(orderByArray) == 0 { if panelType == v3.PanelTypeList { orderByArray = append(orderByArray, constants.TIMESTAMP+" DESC") - } else if panelType == v3.PanelTypeGraph { + } else { orderByArray = append(orderByArray, "value DESC") } } diff --git a/pkg/query-service/app/traces/v4/query_builder_test.go b/pkg/query-service/app/traces/v4/query_builder_test.go index 59fe1998ef..11b5945557 100644 --- a/pkg/query-service/app/traces/v4/query_builder_test.go +++ b/pkg/query-service/app/traces/v4/query_builder_test.go @@ -349,7 +349,7 @@ func Test_orderByAttributeKeyTags(t *testing.T) { items: []v3.OrderBy{{ColumnName: "timestamp", Order: "DESC"}}, tags: []v3.AttributeKey{}, }, - want: "", + want: "value DESC", }, { name: "add default order by ts for list panel", @@ -376,7 +376,7 @@ func Test_orderByAttributeKeyTags(t *testing.T) { items: []v3.OrderBy{}, tags: []v3.AttributeKey{}, }, - want: "", + want: "value DESC", }, } for _, tt := range tests {