mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-04 13:08:46 -06:00
169 lines
5.6 KiB
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
|
|
}
|