signoz/pkg/querybuilder/variable_resolver.go

146 lines
4.1 KiB
Go
Raw Normal View History

2025-06-18 15:30:29 +05:30
package querybuilder
import (
"fmt"
"regexp"
"strings"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
)
// VariableResolver handles variable substitution in query expressions
type VariableResolver struct {
variables map[string]qbtypes.VariableItem
}
// NewVariableResolver creates a new VariableResolver
func NewVariableResolver(variables map[string]qbtypes.VariableItem) *VariableResolver {
return &VariableResolver{
variables: variables,
}
}
// Variable patterns:
// {{.var}} or {{var}}
// [[.var]] or [[var]]
// $var
var variablePatterns = []*regexp.Regexp{
regexp.MustCompile(`\{\{\.?(\w+)\}\}`), // {{.var}} or {{var}}
regexp.MustCompile(`\[\[\.?(\w+)\]\]`), // [[.var]] or [[var]]
regexp.MustCompile(`\$(\w+)`), // $var
}
// IsVariableReference checks if a value is a variable reference
func (r *VariableResolver) IsVariableReference(value string) (bool, string) {
// Check for exact match only (not partial)
for _, pattern := range variablePatterns {
matches := pattern.FindStringSubmatch(value)
if len(matches) > 1 && matches[0] == value {
return true, matches[1]
}
}
return false, ""
}
// ResolveVariable resolves a variable reference to its actual value
func (r *VariableResolver) ResolveVariable(varName string) (any, bool, error) {
item, exists := r.variables[varName]
if !exists {
return nil, false, fmt.Errorf("variable '%s' not found", varName)
}
// Check if this is a dynamic variable with special __all__ value
if item.Type == qbtypes.DynamicVariableType {
// Check for __all__ values which mean "skip filter"
switch v := item.Value.(type) {
case string:
if v == "__all__" {
return nil, true, nil // skip filter
}
case []any:
if len(v) == 1 {
if str, ok := v[0].(string); ok && str == "__all__" {
return nil, true, nil // skip filter
}
}
case []string:
if len(v) == 1 && v[0] == "__all__" {
return nil, true, nil // skip filter
}
}
}
return item.Value, false, nil
}
// ResolveFilterExpression resolves variables in a filter expression
// Returns the resolved expression and whether any filters should be skipped
func (r *VariableResolver) ResolveFilterExpression(expression string) (string, bool, error) {
if expression == "" {
return expression, false, nil
}
// Check if the entire expression is a variable
if isVar, varName := r.IsVariableReference(strings.TrimSpace(expression)); isVar {
value, skipFilter, err := r.ResolveVariable(varName)
if err != nil {
return "", false, err
}
if skipFilter {
return "", true, nil
}
// Convert value to string representation
return formatValue(value), false, nil
}
// For complex expressions, we need to find and replace variable references
// We'll iterate through all variables and check if they appear in the expression
resolvedExpr := expression
for _, pattern := range variablePatterns {
matches := pattern.FindAllStringSubmatch(expression, -1)
for _, match := range matches {
if len(match) > 1 {
varName := match[1]
value, skipFilter, err := r.ResolveVariable(varName)
if err != nil {
// Skip this variable if not found
continue
}
if skipFilter {
// If any variable indicates skip filter, skip the entire filter
return "", true, nil
}
// Replace the variable reference with its value
resolvedExpr = strings.ReplaceAll(resolvedExpr, match[0], formatValue(value))
}
}
}
return resolvedExpr, false, nil
}
// formatValue formats a value for use in a filter expression
func formatValue(value any) string {
switch v := value.(type) {
case string:
// Quote strings
return fmt.Sprintf("'%s'", strings.ReplaceAll(v, "'", "''"))
case []string:
// Format as array
parts := make([]string, len(v))
for i, s := range v {
parts[i] = fmt.Sprintf("'%s'", strings.ReplaceAll(s, "'", "''"))
}
return fmt.Sprintf("[%s]", strings.Join(parts, ", "))
case []any:
// Format as array
parts := make([]string, len(v))
for i, item := range v {
parts[i] = formatValue(item)
}
return fmt.Sprintf("[%s]", strings.Join(parts, ", "))
default:
return fmt.Sprintf("%v", v)
}
}