Files
signoz/pkg/queryparser/queryparserimpl.go

169 lines
5.6 KiB
Go

package queryparser
import (
"context"
"strings"
"github.com/SigNoz/govaluate"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/queryparser/queryfilterextractor"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
)
type queryParserImpl struct {
settings factory.ProviderSettings
}
// New creates a new implementation of the QueryParser service.
func New(settings factory.ProviderSettings) QueryParser {
return &queryParserImpl{
settings: settings,
}
}
func (p *queryParserImpl) AnalyzeQueryFilter(ctx context.Context, queryType qbtypes.QueryType, query string) (*queryfilterextractor.FilterResult, error) {
var extractorType queryfilterextractor.ExtractorType
switch queryType {
case qbtypes.QueryTypePromQL:
extractorType = queryfilterextractor.ExtractorTypePromQL
case qbtypes.QueryTypeClickHouseSQL:
extractorType = queryfilterextractor.ExtractorTypeClickHouseSQL
default:
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported queryType: %s. Supported values are '%s' and '%s'", queryType, qbtypes.QueryTypePromQL, qbtypes.QueryTypeClickHouseSQL)
}
// Create extractor
extractor, err := queryfilterextractor.NewExtractor(extractorType)
if err != nil {
return nil, err
}
return extractor.Extract(query)
}
func (p *queryParserImpl) AnalyzeQueryEnvelopes(ctx context.Context, queries []qbtypes.QueryEnvelope) (map[string]*queryfilterextractor.FilterResult, error) {
results := make(map[string]*queryfilterextractor.FilterResult)
// formulaQueries store the formula queries in the order they are defined
formulaQueries := make(map[string]qbtypes.QueryBuilderFormula)
// First pass: Process non-formula queries
for _, query := range queries {
result := &queryfilterextractor.FilterResult{
MetricNames: []string{},
GroupByColumns: []queryfilterextractor.ColumnInfo{},
}
var queryName string
switch query.Type {
case qbtypes.QueryTypeBuilder:
switch spec := query.Spec.(type) {
case qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]:
queryName = spec.Name
// extract group by fields
for _, groupBy := range spec.GroupBy {
if groupBy.Name != "" {
result.GroupByColumns = append(result.GroupByColumns, queryfilterextractor.ColumnInfo{Name: groupBy.Name, OriginExpr: groupBy.Name, OriginField: groupBy.Name, Alias: groupBy.Name})
}
}
// extract metric names
for _, aggregation := range spec.Aggregations {
if aggregation.MetricName != "" {
result.MetricNames = append(result.MetricNames, aggregation.MetricName)
}
}
default:
// TODO(abhishekhugetech): add support for Traces and Logs Aggregation types
p.settings.Logger.WarnContext(ctx, "unsupported QueryBuilderQuery type: ", spec)
// Skip result for this query
continue
}
case qbtypes.QueryTypePromQL:
spec, ok := query.Spec.(qbtypes.PromQuery)
if !ok || spec.Query == "" {
// Skip result for this query
continue
}
queryName = spec.Name
res, err := p.AnalyzeQueryFilter(ctx, qbtypes.QueryTypePromQL, spec.Query)
if err != nil {
return nil, err
}
result.MetricNames = append(result.MetricNames, res.MetricNames...)
result.GroupByColumns = append(result.GroupByColumns, res.GroupByColumns...)
case qbtypes.QueryTypeClickHouseSQL:
spec, ok := query.Spec.(qbtypes.ClickHouseQuery)
if !ok || spec.Query == "" {
// Skip result for this query
continue
}
queryName = spec.Name
res, err := p.AnalyzeQueryFilter(ctx, qbtypes.QueryTypeClickHouseSQL, spec.Query)
if err != nil {
return nil, err
}
result.MetricNames = append(result.MetricNames, res.MetricNames...)
result.GroupByColumns = append(result.GroupByColumns, res.GroupByColumns...)
case qbtypes.QueryTypeFormula:
spec, ok := query.Spec.(qbtypes.QueryBuilderFormula)
if !ok {
// Skip result for this query
continue
}
formulaQueries[spec.Name] = spec
default:
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported query type: %s", query.Type)
}
if queryName != "" {
results[queryName] = result
}
}
// Second pass: Process formula queries
for _, query := range formulaQueries {
result := &queryfilterextractor.FilterResult{
MetricNames: []string{},
GroupByColumns: []queryfilterextractor.ColumnInfo{},
}
// Parse the expression to find used queries
expression, err := govaluate.NewEvaluableExpressionWithFunctions(query.Expression, qbtypes.EvalFuncs())
if err != nil {
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "failed to parse formula expression %s: %v", query.Name, err)
}
uniqueMetricNames := make(map[string]bool)
uniqueGroupByColumns := make(map[string]bool)
vars := expression.Vars()
for _, v := range vars {
// variables can be "A" or "A.0" or "A.alias" as per pkg/types/querybuildertypes/querybuildertypesv5/formula.go
parts := strings.Split(v, ".")
if len(parts) > 0 {
refQueryName := parts[0]
if refResult, exists := results[refQueryName]; exists {
for _, metricName := range refResult.MetricNames {
if !uniqueMetricNames[metricName] {
uniqueMetricNames[metricName] = true
result.MetricNames = append(result.MetricNames, metricName)
}
}
for _, groupByColumn := range refResult.GroupByColumns {
if !uniqueGroupByColumns[groupByColumn.Name] {
uniqueGroupByColumns[groupByColumn.Name] = true
result.GroupByColumns = append(result.GroupByColumns, groupByColumn)
}
}
}
}
}
// Add the formula query filter result to the results map
results[query.Name] = result
}
return results, nil
}