2025-08-04 21:02:54 +05:30
|
|
|
package variables
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
grammar "github.com/SigNoz/signoz/pkg/parser/grammar"
|
|
|
|
|
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
|
|
|
|
"github.com/antlr4-go/antlr/v4"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ErrorListener collects syntax errors during parsing
|
|
|
|
|
type ErrorListener struct {
|
|
|
|
|
*antlr.DefaultErrorListener
|
|
|
|
|
SyntaxErrors []error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewErrorListener creates a new error listener
|
|
|
|
|
func NewErrorListener() *ErrorListener {
|
|
|
|
|
return &ErrorListener{
|
|
|
|
|
DefaultErrorListener: antlr.NewDefaultErrorListener(),
|
|
|
|
|
SyntaxErrors: []error{},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SyntaxError is called when a syntax error is encountered
|
|
|
|
|
func (e *ErrorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol any, line, column int, msg string, ex antlr.RecognitionException) {
|
|
|
|
|
e.SyntaxErrors = append(e.SyntaxErrors, fmt.Errorf("line %d:%d %s", line, column, msg))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// variableReplacementVisitor implements the visitor interface
|
|
|
|
|
// to replace variables in filter expressions with their actual values
|
|
|
|
|
type variableReplacementVisitor struct {
|
|
|
|
|
variables map[string]qbtypes.VariableItem
|
|
|
|
|
errors []string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// specialSkipMarker is used to indicate that a condition should be removed
|
|
|
|
|
const specialSkipMarker = "__SKIP_CONDITION__"
|
|
|
|
|
|
|
|
|
|
// ReplaceVariablesInExpression takes a filter expression and returns it with variables replaced
|
|
|
|
|
func ReplaceVariablesInExpression(expression string, variables map[string]qbtypes.VariableItem) (string, error) {
|
|
|
|
|
input := antlr.NewInputStream(expression)
|
|
|
|
|
lexer := grammar.NewFilterQueryLexer(input)
|
|
|
|
|
|
|
|
|
|
visitor := &variableReplacementVisitor{
|
|
|
|
|
variables: variables,
|
|
|
|
|
errors: []string{},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lexerErrorListener := NewErrorListener()
|
|
|
|
|
lexer.RemoveErrorListeners()
|
|
|
|
|
lexer.AddErrorListener(lexerErrorListener)
|
|
|
|
|
|
|
|
|
|
tokens := antlr.NewCommonTokenStream(lexer, 0)
|
|
|
|
|
parserErrorListener := NewErrorListener()
|
|
|
|
|
parser := grammar.NewFilterQueryParser(tokens)
|
|
|
|
|
parser.RemoveErrorListeners()
|
|
|
|
|
parser.AddErrorListener(parserErrorListener)
|
|
|
|
|
|
|
|
|
|
tree := parser.Query()
|
|
|
|
|
|
|
|
|
|
if len(parserErrorListener.SyntaxErrors) > 0 {
|
|
|
|
|
return "", fmt.Errorf("syntax errors in expression: %v", parserErrorListener.SyntaxErrors)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result := visitor.Visit(tree).(string)
|
|
|
|
|
|
|
|
|
|
if len(visitor.errors) > 0 {
|
|
|
|
|
return "", fmt.Errorf("errors processing expression: %v", visitor.errors)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the entire expression should be skipped, return empty string
|
|
|
|
|
if result == specialSkipMarker {
|
|
|
|
|
return "", nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Visit dispatches to the specific visit method based on node type
|
|
|
|
|
func (v *variableReplacementVisitor) Visit(tree antlr.ParseTree) any {
|
|
|
|
|
if tree == nil {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch t := tree.(type) {
|
|
|
|
|
case *grammar.QueryContext:
|
|
|
|
|
return v.VisitQuery(t)
|
|
|
|
|
case *grammar.ExpressionContext:
|
|
|
|
|
return v.VisitExpression(t)
|
|
|
|
|
case *grammar.OrExpressionContext:
|
|
|
|
|
return v.VisitOrExpression(t)
|
|
|
|
|
case *grammar.AndExpressionContext:
|
|
|
|
|
return v.VisitAndExpression(t)
|
|
|
|
|
case *grammar.UnaryExpressionContext:
|
|
|
|
|
return v.VisitUnaryExpression(t)
|
|
|
|
|
case *grammar.PrimaryContext:
|
|
|
|
|
return v.VisitPrimary(t)
|
|
|
|
|
case *grammar.ComparisonContext:
|
|
|
|
|
return v.VisitComparison(t)
|
|
|
|
|
case *grammar.InClauseContext:
|
|
|
|
|
return v.VisitInClause(t)
|
|
|
|
|
case *grammar.NotInClauseContext:
|
|
|
|
|
return v.VisitNotInClause(t)
|
|
|
|
|
case *grammar.ValueListContext:
|
|
|
|
|
return v.VisitValueList(t)
|
|
|
|
|
case *grammar.FullTextContext:
|
|
|
|
|
return v.VisitFullText(t)
|
|
|
|
|
case *grammar.FunctionCallContext:
|
|
|
|
|
return v.VisitFunctionCall(t)
|
|
|
|
|
case *grammar.FunctionParamListContext:
|
|
|
|
|
return v.VisitFunctionParamList(t)
|
|
|
|
|
case *grammar.FunctionParamContext:
|
|
|
|
|
return v.VisitFunctionParam(t)
|
|
|
|
|
case *grammar.ArrayContext:
|
|
|
|
|
return v.VisitArray(t)
|
|
|
|
|
case *grammar.ValueContext:
|
|
|
|
|
return v.VisitValue(t)
|
|
|
|
|
case *grammar.KeyContext:
|
|
|
|
|
return v.VisitKey(t)
|
|
|
|
|
default:
|
|
|
|
|
// For unknown types, return the original text
|
|
|
|
|
return tree.GetText()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitQuery(ctx *grammar.QueryContext) any {
|
|
|
|
|
return v.Visit(ctx.Expression())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitExpression(ctx *grammar.ExpressionContext) any {
|
|
|
|
|
return v.Visit(ctx.OrExpression())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitOrExpression(ctx *grammar.OrExpressionContext) any {
|
|
|
|
|
andExpressions := ctx.AllAndExpression()
|
|
|
|
|
|
|
|
|
|
parts := make([]string, 0, len(andExpressions))
|
|
|
|
|
for _, expr := range andExpressions {
|
|
|
|
|
part := v.Visit(expr).(string)
|
|
|
|
|
// Skip conditions that should be removed
|
|
|
|
|
if part != specialSkipMarker && part != "" {
|
|
|
|
|
parts = append(parts, part)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(parts) == 0 {
|
|
|
|
|
return specialSkipMarker
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(parts) == 1 {
|
|
|
|
|
return parts[0]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return strings.Join(parts, " OR ")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitAndExpression(ctx *grammar.AndExpressionContext) any {
|
|
|
|
|
unaryExpressions := ctx.AllUnaryExpression()
|
|
|
|
|
|
|
|
|
|
parts := make([]string, 0, len(unaryExpressions))
|
|
|
|
|
for _, expr := range unaryExpressions {
|
|
|
|
|
part := v.Visit(expr).(string)
|
|
|
|
|
// Skip conditions that should be removed
|
|
|
|
|
if part != specialSkipMarker && part != "" {
|
|
|
|
|
parts = append(parts, part)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(parts) == 0 {
|
|
|
|
|
return specialSkipMarker
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(parts) == 1 {
|
|
|
|
|
return parts[0]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return strings.Join(parts, " AND ")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitUnaryExpression(ctx *grammar.UnaryExpressionContext) any {
|
|
|
|
|
result := v.Visit(ctx.Primary()).(string)
|
|
|
|
|
|
|
|
|
|
// If the condition should be skipped, propagate it up
|
|
|
|
|
if result == specialSkipMarker {
|
|
|
|
|
return specialSkipMarker
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ctx.NOT() != nil {
|
|
|
|
|
return "NOT " + result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitPrimary(ctx *grammar.PrimaryContext) any {
|
|
|
|
|
if ctx.OrExpression() != nil {
|
|
|
|
|
return "(" + v.Visit(ctx.OrExpression()).(string) + ")"
|
|
|
|
|
} else if ctx.Comparison() != nil {
|
|
|
|
|
return v.Visit(ctx.Comparison())
|
|
|
|
|
} else if ctx.FunctionCall() != nil {
|
|
|
|
|
return v.Visit(ctx.FunctionCall())
|
|
|
|
|
} else if ctx.FullText() != nil {
|
|
|
|
|
return v.Visit(ctx.FullText())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle standalone key/value
|
|
|
|
|
if ctx.GetChildCount() == 1 {
|
|
|
|
|
child := ctx.GetChild(0)
|
|
|
|
|
if parseTree, ok := child.(antlr.ParseTree); ok {
|
|
|
|
|
return v.Visit(parseTree).(string)
|
|
|
|
|
}
|
|
|
|
|
// Fallback to getting text from the context
|
|
|
|
|
return ctx.GetText()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ctx.GetText()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitComparison(ctx *grammar.ComparisonContext) any {
|
|
|
|
|
// First check if any value contains __all__ variable
|
|
|
|
|
values := ctx.AllValue()
|
|
|
|
|
for _, val := range values {
|
|
|
|
|
valueResult := v.Visit(val).(string)
|
|
|
|
|
if valueResult == specialSkipMarker {
|
|
|
|
|
return specialSkipMarker
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Also check in IN/NOT IN clauses
|
|
|
|
|
if ctx.InClause() != nil {
|
|
|
|
|
inResult := v.Visit(ctx.InClause()).(string)
|
|
|
|
|
if inResult == specialSkipMarker {
|
|
|
|
|
return specialSkipMarker
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ctx.NotInClause() != nil {
|
|
|
|
|
notInResult := v.Visit(ctx.NotInClause()).(string)
|
|
|
|
|
if notInResult == specialSkipMarker {
|
|
|
|
|
return specialSkipMarker
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var parts []string
|
|
|
|
|
|
|
|
|
|
// Add key
|
|
|
|
|
parts = append(parts, v.Visit(ctx.Key()).(string))
|
|
|
|
|
|
|
|
|
|
// Handle EXISTS
|
|
|
|
|
if ctx.EXISTS() != nil {
|
|
|
|
|
if ctx.NOT() != nil {
|
|
|
|
|
parts = append(parts, " NOT")
|
|
|
|
|
}
|
|
|
|
|
parts = append(parts, " EXISTS")
|
|
|
|
|
return strings.Join(parts, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle IN/NOT IN
|
|
|
|
|
if ctx.InClause() != nil {
|
|
|
|
|
parts = append(parts, " IN ")
|
|
|
|
|
parts = append(parts, v.Visit(ctx.InClause()).(string))
|
|
|
|
|
return strings.Join(parts, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ctx.NotInClause() != nil {
|
|
|
|
|
parts = append(parts, " NOT IN ")
|
|
|
|
|
parts = append(parts, v.Visit(ctx.NotInClause()).(string))
|
|
|
|
|
return strings.Join(parts, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle BETWEEN
|
|
|
|
|
if ctx.BETWEEN() != nil {
|
|
|
|
|
if ctx.NOT() != nil {
|
|
|
|
|
parts = append(parts, " NOT")
|
|
|
|
|
}
|
|
|
|
|
parts = append(parts, " BETWEEN ")
|
|
|
|
|
values := ctx.AllValue()
|
|
|
|
|
parts = append(parts, v.Visit(values[0]).(string))
|
|
|
|
|
parts = append(parts, " AND ")
|
|
|
|
|
parts = append(parts, v.Visit(values[1]).(string))
|
|
|
|
|
return strings.Join(parts, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle other operators
|
|
|
|
|
if ctx.EQUALS() != nil {
|
|
|
|
|
parts = append(parts, " = ")
|
|
|
|
|
} else if ctx.NOT_EQUALS() != nil {
|
|
|
|
|
parts = append(parts, " != ")
|
|
|
|
|
} else if ctx.NEQ() != nil {
|
|
|
|
|
parts = append(parts, " <> ")
|
|
|
|
|
} else if ctx.LT() != nil {
|
|
|
|
|
parts = append(parts, " < ")
|
|
|
|
|
} else if ctx.LE() != nil {
|
|
|
|
|
parts = append(parts, " <= ")
|
|
|
|
|
} else if ctx.GT() != nil {
|
|
|
|
|
parts = append(parts, " > ")
|
|
|
|
|
} else if ctx.GE() != nil {
|
|
|
|
|
parts = append(parts, " >= ")
|
|
|
|
|
} else if ctx.LIKE() != nil {
|
2025-08-26 11:28:31 +05:30
|
|
|
if ctx.NOT() != nil {
|
|
|
|
|
parts = append(parts, " NOT")
|
|
|
|
|
}
|
2025-08-04 21:02:54 +05:30
|
|
|
parts = append(parts, " LIKE ")
|
|
|
|
|
} else if ctx.ILIKE() != nil {
|
2025-08-26 11:28:31 +05:30
|
|
|
if ctx.NOT() != nil {
|
|
|
|
|
parts = append(parts, " NOT")
|
|
|
|
|
}
|
2025-08-04 21:02:54 +05:30
|
|
|
parts = append(parts, " ILIKE ")
|
|
|
|
|
} else if ctx.REGEXP() != nil {
|
|
|
|
|
if ctx.NOT() != nil {
|
|
|
|
|
parts = append(parts, " NOT")
|
|
|
|
|
}
|
|
|
|
|
parts = append(parts, " REGEXP ")
|
|
|
|
|
} else if ctx.CONTAINS() != nil {
|
|
|
|
|
if ctx.NOT() != nil {
|
|
|
|
|
parts = append(parts, " NOT")
|
|
|
|
|
}
|
|
|
|
|
parts = append(parts, " CONTAINS ")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add value
|
|
|
|
|
if len(values) > 0 {
|
|
|
|
|
parts = append(parts, v.Visit(values[0]).(string))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return strings.Join(parts, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitInClause(ctx *grammar.InClauseContext) any {
|
|
|
|
|
if ctx.ValueList() != nil {
|
|
|
|
|
result := v.Visit(ctx.ValueList()).(string)
|
|
|
|
|
if result == specialSkipMarker {
|
|
|
|
|
return specialSkipMarker
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
result := v.Visit(ctx.Value()).(string)
|
|
|
|
|
if result == specialSkipMarker {
|
|
|
|
|
return specialSkipMarker
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitNotInClause(ctx *grammar.NotInClauseContext) any {
|
|
|
|
|
if ctx.ValueList() != nil {
|
|
|
|
|
result := v.Visit(ctx.ValueList()).(string)
|
|
|
|
|
if result == specialSkipMarker {
|
|
|
|
|
return specialSkipMarker
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
result := v.Visit(ctx.Value()).(string)
|
|
|
|
|
if result == specialSkipMarker {
|
|
|
|
|
return specialSkipMarker
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitValueList(ctx *grammar.ValueListContext) any {
|
|
|
|
|
values := ctx.AllValue()
|
|
|
|
|
|
|
|
|
|
// Check if any value is __all__
|
|
|
|
|
for _, val := range values {
|
|
|
|
|
result := v.Visit(val).(string)
|
|
|
|
|
if result == specialSkipMarker {
|
|
|
|
|
return specialSkipMarker
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parts := make([]string, 0, len(values))
|
|
|
|
|
for i, val := range values {
|
|
|
|
|
if i > 0 {
|
|
|
|
|
parts = append(parts, ", ")
|
|
|
|
|
}
|
|
|
|
|
parts = append(parts, v.Visit(val).(string))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return "(" + strings.Join(parts, "") + ")"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitFullText(ctx *grammar.FullTextContext) any {
|
|
|
|
|
if ctx.QUOTED_TEXT() != nil {
|
|
|
|
|
return ctx.QUOTED_TEXT().GetText()
|
|
|
|
|
} else if ctx.FREETEXT() != nil {
|
|
|
|
|
return ctx.FREETEXT().GetText()
|
|
|
|
|
}
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitFunctionCall(ctx *grammar.FunctionCallContext) any {
|
|
|
|
|
var functionName string
|
|
|
|
|
if ctx.HAS() != nil {
|
|
|
|
|
functionName = "has"
|
|
|
|
|
} else if ctx.HASANY() != nil {
|
|
|
|
|
functionName = "hasAny"
|
|
|
|
|
} else if ctx.HASALL() != nil {
|
|
|
|
|
functionName = "hasAll"
|
2025-08-26 11:28:31 +05:30
|
|
|
} else if ctx.HASTOKEN() != nil {
|
|
|
|
|
functionName = "hasToken"
|
2025-08-04 21:02:54 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
params := v.Visit(ctx.FunctionParamList()).(string)
|
|
|
|
|
return functionName + "(" + params + ")"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitFunctionParamList(ctx *grammar.FunctionParamListContext) any {
|
|
|
|
|
params := ctx.AllFunctionParam()
|
|
|
|
|
parts := make([]string, 0, len(params))
|
|
|
|
|
|
|
|
|
|
for i, param := range params {
|
|
|
|
|
if i > 0 {
|
|
|
|
|
parts = append(parts, ", ")
|
|
|
|
|
}
|
|
|
|
|
parts = append(parts, v.Visit(param).(string))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return strings.Join(parts, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitFunctionParam(ctx *grammar.FunctionParamContext) any {
|
|
|
|
|
if ctx.Key() != nil {
|
|
|
|
|
return v.Visit(ctx.Key())
|
|
|
|
|
} else if ctx.Value() != nil {
|
|
|
|
|
return v.Visit(ctx.Value())
|
|
|
|
|
} else if ctx.Array() != nil {
|
|
|
|
|
return v.Visit(ctx.Array())
|
|
|
|
|
}
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitArray(ctx *grammar.ArrayContext) any {
|
|
|
|
|
valueList := v.Visit(ctx.ValueList()).(string)
|
|
|
|
|
// Don't wrap in brackets if it's already wrapped in parentheses
|
|
|
|
|
if strings.HasPrefix(valueList, "(") {
|
|
|
|
|
return valueList
|
|
|
|
|
}
|
|
|
|
|
return "[" + valueList + "]"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitValue(ctx *grammar.ValueContext) any {
|
|
|
|
|
// First get the original value
|
|
|
|
|
var originalValue string
|
|
|
|
|
if ctx.QUOTED_TEXT() != nil {
|
2025-09-07 14:10:11 +05:30
|
|
|
quotedText := ctx.QUOTED_TEXT().GetText()
|
|
|
|
|
originalValue = trimQuotes(quotedText)
|
2025-08-04 21:02:54 +05:30
|
|
|
} else if ctx.NUMBER() != nil {
|
|
|
|
|
originalValue = ctx.NUMBER().GetText()
|
|
|
|
|
} else if ctx.KEY() != nil {
|
|
|
|
|
originalValue = ctx.KEY().GetText()
|
2025-09-07 14:10:11 +05:30
|
|
|
} else if ctx.BOOL() != nil {
|
|
|
|
|
originalValue = ctx.BOOL().GetText()
|
2025-08-04 21:02:54 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if this is a variable (starts with $)
|
|
|
|
|
if strings.HasPrefix(originalValue, "$") {
|
|
|
|
|
varName := originalValue
|
|
|
|
|
|
|
|
|
|
// Try with $ prefix first
|
|
|
|
|
varItem, ok := v.variables[varName]
|
|
|
|
|
if !ok && len(varName) > 1 {
|
|
|
|
|
// Try without $ prefix
|
|
|
|
|
varItem, ok = v.variables[varName[1:]]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
|
// Handle dynamic variable with __all__ value
|
|
|
|
|
if varItem.Type == qbtypes.DynamicVariableType {
|
|
|
|
|
if allVal, ok := varItem.Value.(string); ok && allVal == "__all__" {
|
|
|
|
|
// Return special marker to indicate this condition should be removed
|
|
|
|
|
return specialSkipMarker
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Replace variable with its value
|
|
|
|
|
return v.formatVariableValue(varItem.Value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return original value if not a variable or variable not found
|
2025-09-07 14:10:11 +05:30
|
|
|
// If it was quoted text and not a variable, return with quotes
|
|
|
|
|
if ctx.QUOTED_TEXT() != nil && !strings.HasPrefix(originalValue, "$") {
|
|
|
|
|
return ctx.QUOTED_TEXT().GetText()
|
|
|
|
|
}
|
2025-08-04 21:02:54 +05:30
|
|
|
return originalValue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *variableReplacementVisitor) VisitKey(ctx *grammar.KeyContext) any {
|
|
|
|
|
keyText := ctx.GetText()
|
|
|
|
|
|
|
|
|
|
// Check if this key is actually a variable
|
|
|
|
|
if strings.HasPrefix(keyText, "$") {
|
|
|
|
|
varName := keyText
|
|
|
|
|
|
|
|
|
|
// Try with $ prefix first
|
|
|
|
|
varItem, ok := v.variables[varName]
|
|
|
|
|
if !ok && len(varName) > 1 {
|
|
|
|
|
// Try without $ prefix
|
|
|
|
|
varItem, ok = v.variables[varName[1:]]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
|
// Handle dynamic variable with __all__ value
|
|
|
|
|
if varItem.Type == qbtypes.DynamicVariableType {
|
|
|
|
|
if allVal, ok := varItem.Value.(string); ok && allVal == "__all__" {
|
|
|
|
|
return specialSkipMarker
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Replace variable with its value
|
|
|
|
|
return v.formatVariableValue(varItem.Value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return keyText
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// formatVariableValue formats a variable value for inclusion in the expression
|
|
|
|
|
func (v *variableReplacementVisitor) formatVariableValue(value any) string {
|
|
|
|
|
switch val := value.(type) {
|
|
|
|
|
case string:
|
|
|
|
|
// Quote string values
|
|
|
|
|
return fmt.Sprintf("'%s'", strings.ReplaceAll(val, "'", "\\'"))
|
2025-09-07 14:10:11 +05:30
|
|
|
case []string:
|
|
|
|
|
parts := make([]string, len(val))
|
|
|
|
|
for i, item := range val {
|
|
|
|
|
parts[i] = fmt.Sprintf("'%s'", strings.ReplaceAll(item, "'", "\\'"))
|
|
|
|
|
}
|
|
|
|
|
return "[" + strings.Join(parts, ", ") + "]"
|
2025-08-04 21:02:54 +05:30
|
|
|
case []any:
|
|
|
|
|
// Format array values
|
|
|
|
|
parts := make([]string, len(val))
|
|
|
|
|
for i, item := range val {
|
|
|
|
|
parts[i] = v.formatVariableValue(item)
|
|
|
|
|
}
|
2025-09-07 14:10:11 +05:30
|
|
|
return "[" + strings.Join(parts, ", ") + "]"
|
2025-08-04 21:02:54 +05:30
|
|
|
case int, int32, int64, float32, float64:
|
|
|
|
|
return fmt.Sprintf("%v", val)
|
|
|
|
|
case bool:
|
|
|
|
|
return strconv.FormatBool(val)
|
|
|
|
|
default:
|
|
|
|
|
return fmt.Sprintf("%v", val)
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-07 14:10:11 +05:30
|
|
|
|
|
|
|
|
func trimQuotes(s string) string {
|
|
|
|
|
if len(s) >= 2 {
|
|
|
|
|
if (s[0] == '"' && s[len(s)-1] == '"') || (s[0] == '\'' && s[len(s)-1] == '\'') {
|
|
|
|
|
return s[1 : len(s)-1]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return s
|
|
|
|
|
}
|