diff --git a/pkg/modules/tracefunnel/query.go b/pkg/modules/tracefunnel/query.go index 290b7c7b5d..0a083e9c7b 100644 --- a/pkg/modules/tracefunnel/query.go +++ b/pkg/modules/tracefunnel/query.go @@ -2,12 +2,21 @@ package tracefunnel import ( "fmt" + "strings" + tracev4 "github.com/SigNoz/signoz/pkg/query-service/app/traces/v4" v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3" "github.com/SigNoz/signoz/pkg/types/tracefunneltypes" - "strings" ) +// sanitizeClause adds AND prefix to non-empty clauses +func sanitizeClause(clause string) string { + if clause == "" { + return "" + } + return "AND " + clause +} + func ValidateTraces(funnel *tracefunneltypes.StorableFunnel, timeRange tracefunneltypes.TimeRange) (*v3.ClickHouseQuery, error) { var query string var err error @@ -28,22 +37,27 @@ func ValidateTraces(funnel *tracefunneltypes.StorableFunnel, timeRange tracefunn } // Build filter clauses for each step - clauseStep1, err := tracev4.BuildTracesFilterQuery(funnelSteps[0].Filters) + clauseStep1, err := tracev4.BuildTracesFilter(funnelSteps[0].Filters) if err != nil { return nil, err } - clauseStep2, err := tracev4.BuildTracesFilterQuery(funnelSteps[1].Filters) + clauseStep2, err := tracev4.BuildTracesFilter(funnelSteps[1].Filters) if err != nil { return nil, err } clauseStep3 := "" if len(funnel.Steps) > 2 { - clauseStep3, err = tracev4.BuildTracesFilterQuery(funnelSteps[2].Filters) + clauseStep3, err = tracev4.BuildTracesFilter(funnelSteps[2].Filters) if err != nil { return nil, err } } + // Sanitize clauses + clauseStep1 = sanitizeClause(clauseStep1) + clauseStep2 = sanitizeClause(clauseStep2) + clauseStep3 = sanitizeClause(clauseStep3) + if len(funnel.Steps) > 2 { query = BuildThreeStepFunnelValidationQuery( containsErrorT1, @@ -107,22 +121,27 @@ func GetFunnelAnalytics(funnel *tracefunneltypes.StorableFunnel, timeRange trace } // Build filter clauses for each step - clauseStep1, err := tracev4.BuildTracesFilterQuery(funnelSteps[0].Filters) + clauseStep1, err := tracev4.BuildTracesFilter(funnelSteps[0].Filters) if err != nil { return nil, err } - clauseStep2, err := tracev4.BuildTracesFilterQuery(funnelSteps[1].Filters) + clauseStep2, err := tracev4.BuildTracesFilter(funnelSteps[1].Filters) if err != nil { return nil, err } clauseStep3 := "" if len(funnel.Steps) > 2 { - clauseStep3, err = tracev4.BuildTracesFilterQuery(funnelSteps[2].Filters) + clauseStep3, err = tracev4.BuildTracesFilter(funnelSteps[2].Filters) if err != nil { return nil, err } } + // Sanitize clauses + clauseStep1 = sanitizeClause(clauseStep1) + clauseStep2 = sanitizeClause(clauseStep2) + clauseStep3 = sanitizeClause(clauseStep3) + if len(funnel.Steps) > 2 { query = BuildThreeStepFunnelOverviewQuery( containsErrorT1, @@ -215,22 +234,27 @@ func GetFunnelStepAnalytics(funnel *tracefunneltypes.StorableFunnel, timeRange t } // Build filter clauses for each step - clauseStep1, err := tracev4.BuildTracesFilterQuery(funnelSteps[0].Filters) + clauseStep1, err := tracev4.BuildTracesFilter(funnelSteps[0].Filters) if err != nil { return nil, err } - clauseStep2, err := tracev4.BuildTracesFilterQuery(funnelSteps[1].Filters) + clauseStep2, err := tracev4.BuildTracesFilter(funnelSteps[1].Filters) if err != nil { return nil, err } clauseStep3 := "" if len(funnel.Steps) > 2 { - clauseStep3, err = tracev4.BuildTracesFilterQuery(funnelSteps[2].Filters) + clauseStep3, err = tracev4.BuildTracesFilter(funnelSteps[2].Filters) if err != nil { return nil, err } } + // Sanitize clauses + clauseStep1 = sanitizeClause(clauseStep1) + clauseStep2 = sanitizeClause(clauseStep2) + clauseStep3 = sanitizeClause(clauseStep3) + if len(funnel.Steps) > 2 { query = BuildThreeStepFunnelStepOverviewQuery( containsErrorT1, @@ -294,22 +318,27 @@ func GetStepAnalytics(funnel *tracefunneltypes.StorableFunnel, timeRange tracefu } // Build filter clauses for each step - clauseStep1, err := tracev4.BuildTracesFilterQuery(funnelSteps[0].Filters) + clauseStep1, err := tracev4.BuildTracesFilter(funnelSteps[0].Filters) if err != nil { return nil, err } - clauseStep2, err := tracev4.BuildTracesFilterQuery(funnelSteps[1].Filters) + clauseStep2, err := tracev4.BuildTracesFilter(funnelSteps[1].Filters) if err != nil { return nil, err } clauseStep3 := "" if len(funnel.Steps) > 2 { - clauseStep3, err = tracev4.BuildTracesFilterQuery(funnelSteps[2].Filters) + clauseStep3, err = tracev4.BuildTracesFilter(funnelSteps[2].Filters) if err != nil { return nil, err } } + // Sanitize clauses + clauseStep1 = sanitizeClause(clauseStep1) + clauseStep2 = sanitizeClause(clauseStep2) + clauseStep3 = sanitizeClause(clauseStep3) + if len(funnel.Steps) > 2 { query = BuildThreeStepFunnelCountQuery( containsErrorT1, @@ -366,15 +395,19 @@ func GetSlowestTraces(funnel *tracefunneltypes.StorableFunnel, timeRange tracefu } // Build filter clauses for the steps - clauseStep1, err := tracev4.BuildTracesFilterQuery(funnelSteps[stepStartOrder].Filters) + clauseStep1, err := tracev4.BuildTracesFilter(funnelSteps[stepStartOrder].Filters) if err != nil { return nil, err } - clauseStep2, err := tracev4.BuildTracesFilterQuery(funnelSteps[stepEndOrder].Filters) + clauseStep2, err := tracev4.BuildTracesFilter(funnelSteps[stepEndOrder].Filters) if err != nil { return nil, err } + // Sanitize clauses + clauseStep1 = sanitizeClause(clauseStep1) + clauseStep2 = sanitizeClause(clauseStep2) + query := BuildTwoStepFunnelTopSlowTracesQuery( containsErrorT1, containsErrorT2, @@ -409,15 +442,19 @@ func GetErroredTraces(funnel *tracefunneltypes.StorableFunnel, timeRange tracefu } // Build filter clauses for the steps - clauseStep1, err := tracev4.BuildTracesFilterQuery(funnelSteps[stepStartOrder].Filters) + clauseStep1, err := tracev4.BuildTracesFilter(funnelSteps[stepStartOrder].Filters) if err != nil { return nil, err } - clauseStep2, err := tracev4.BuildTracesFilterQuery(funnelSteps[stepEndOrder].Filters) + clauseStep2, err := tracev4.BuildTracesFilter(funnelSteps[stepEndOrder].Filters) if err != nil { return nil, err } + // Sanitize clauses + clauseStep1 = sanitizeClause(clauseStep1) + clauseStep2 = sanitizeClause(clauseStep2) + query := BuildTwoStepFunnelTopSlowErrorTracesQuery( containsErrorT1, containsErrorT2, diff --git a/pkg/query-service/app/traces/v4/query_builder.go b/pkg/query-service/app/traces/v4/query_builder.go index 69a76de4d1..a563617a78 100644 --- a/pkg/query-service/app/traces/v4/query_builder.go +++ b/pkg/query-service/app/traces/v4/query_builder.go @@ -87,6 +87,62 @@ func existsSubQueryForFixedColumn(key v3.AttributeKey, op v3.FilterOperator) (st } } +func BuildTracesFilter(fs *v3.FilterSet) (string, error) { + var conditions []string + + if fs != nil && len(fs.Items) != 0 { + for _, item := range fs.Items { + + 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: + // 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)) + case v3.FilterOperatorExists, v3.FilterOperatorNotExists: + if item.Key.IsColumn { + subQuery, err := 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 BuildTracesFilterQuery(fs *v3.FilterSet) (string, error) { var conditions []string