diff --git a/pkg/ottl/grammar.go b/pkg/ottl/grammar.go index 369eff00ecbd..2407a138c1c2 100644 --- a/pkg/ottl/grammar.go +++ b/pkg/ottl/grammar.go @@ -57,6 +57,18 @@ func (b *booleanValue) checkForCustomError() error { return nil } +func (b *booleanValue) accept(v grammarVisitor) { + if b.Comparison != nil { + b.Comparison.accept(v) + } + if b.ConstExpr != nil && b.ConstExpr.Converter != nil { + b.ConstExpr.Converter.accept(v) + } + if b.SubExpr != nil { + b.SubExpr.accept(v) + } +} + // opAndBooleanValue represents the right side of an AND boolean expression. type opAndBooleanValue struct { Operator string `parser:"@OpAnd"` @@ -67,6 +79,12 @@ func (b *opAndBooleanValue) checkForCustomError() error { return b.Value.checkForCustomError() } +func (b *opAndBooleanValue) accept(v grammarVisitor) { + if b.Value != nil { + b.Value.accept(v) + } +} + // term represents an arbitrary number of boolean values joined by AND. type term struct { Left *booleanValue `parser:"@@"` @@ -87,6 +105,17 @@ func (b *term) checkForCustomError() error { return nil } +func (b *term) accept(v grammarVisitor) { + if b.Left != nil { + b.Left.accept(v) + } + for _, r := range b.Right { + if r != nil { + r.accept(v) + } + } +} + // opOrTerm represents the right side of an OR boolean expression. type opOrTerm struct { Operator string `parser:"@OpOr"` @@ -97,6 +126,12 @@ func (b *opOrTerm) checkForCustomError() error { return b.Term.checkForCustomError() } +func (b *opOrTerm) accept(v grammarVisitor) { + if b.Term != nil { + b.Term.accept(v) + } +} + // booleanExpression represents a true/false decision expressed // as an arbitrary number of terms separated by OR. type booleanExpression struct { @@ -118,6 +153,17 @@ func (b *booleanExpression) checkForCustomError() error { return nil } +func (b *booleanExpression) accept(v grammarVisitor) { + if b.Left != nil { + b.Left.accept(v) + } + for _, r := range b.Right { + if r != nil { + r.accept(v) + } + } +} + // compareOp is the type of a comparison operator. type compareOp int @@ -187,6 +233,11 @@ func (c *comparison) checkForCustomError() error { return err } +func (c *comparison) accept(v grammarVisitor) { + c.Left.accept(v) + c.Right.accept(v) +} + // editor represents the function call of a statement. type editor struct { Function string `parser:"@(Lowercase(Uppercase | Lowercase)*)"` @@ -210,6 +261,13 @@ func (i *editor) checkForCustomError() error { return nil } +func (i *editor) accept(v grammarVisitor) { + v.visitEditor(i) + for _, arg := range i.Arguments { + arg.accept(v) + } +} + // converter represents a converter function call. type converter struct { Function string `parser:"@(Uppercase(Uppercase | Lowercase)*)"` @@ -217,6 +275,14 @@ type converter struct { Keys []key `parser:"( @@ )*"` } +func (c *converter) accept(v grammarVisitor) { + if c.Arguments != nil { + for _, a := range c.Arguments { + a.accept(v) + } + } +} + type argument struct { Name string `parser:"(@(Lowercase(Uppercase | Lowercase)*) Equal)?"` Value value `parser:"( @@"` @@ -227,6 +293,10 @@ func (a *argument) checkForCustomError() error { return a.Value.checkForCustomError() } +func (a *argument) accept(v grammarVisitor) { + a.Value.accept(v) +} + // value represents a part of a parsed statement which is resolved to a value of some sort. This can be a telemetry path // mathExpression, function call, or literal. type value struct { @@ -251,8 +321,27 @@ func (v *value) checkForCustomError() error { return nil } +func (v *value) accept(vis grammarVisitor) { + vis.visitValue(v) + if v.Literal != nil { + v.Literal.accept(vis) + } + if v.MathExpression != nil { + v.MathExpression.accept(vis) + } + if v.Map != nil { + v.Map.accept(vis) + } + if v.List != nil { + for _, i := range v.List.Values { + i.accept(vis) + } + } +} + // path represents a telemetry path mathExpression. type path struct { + Pos lexer.Position Context string `parser:"(@Lowercase '.')?"` Fields []field `parser:"@@ ( '.' @@ )*"` } @@ -276,6 +365,14 @@ type mapValue struct { Values []mapItem `parser:"'{' (@@ ','?)* '}'"` } +func (m *mapValue) accept(v grammarVisitor) { + for _, i := range m.Values { + if i.Value != nil { + i.Value.accept(v) + } + } +} + type mapItem struct { Key *string `parser:"@String ':'"` Value *value `parser:"@@"` @@ -326,6 +423,19 @@ func (m *mathExprLiteral) checkForCustomError() error { return nil } +func (m *mathExprLiteral) accept(v grammarVisitor) { + v.visitMathExprLiteral(m) + if m.Path != nil { + v.visitPath(m.Path) + } + if m.Editor != nil { + m.Editor.accept(v) + } + if m.Converter != nil { + m.Converter.accept(v) + } +} + type mathValue struct { Literal *mathExprLiteral `parser:"( @@"` SubExpression *mathExpression `parser:"| '(' @@ ')' )"` @@ -338,6 +448,15 @@ func (m *mathValue) checkForCustomError() error { return m.SubExpression.checkForCustomError() } +func (m *mathValue) accept(v grammarVisitor) { + if m.Literal != nil { + m.Literal.accept(v) + } + if m.SubExpression != nil { + m.SubExpression.accept(v) + } +} + type opMultDivValue struct { Operator mathOp `parser:"@OpMultDiv"` Value *mathValue `parser:"@@"` @@ -347,6 +466,12 @@ func (m *opMultDivValue) checkForCustomError() error { return m.Value.checkForCustomError() } +func (m *opMultDivValue) accept(v grammarVisitor) { + if m.Value != nil { + m.Value.accept(v) + } +} + type addSubTerm struct { Left *mathValue `parser:"@@"` Right []*opMultDivValue `parser:"@@*"` @@ -366,6 +491,17 @@ func (m *addSubTerm) checkForCustomError() error { return nil } +func (m *addSubTerm) accept(v grammarVisitor) { + if m.Left != nil { + m.Left.accept(v) + } + for _, r := range m.Right { + if r != nil { + r.accept(v) + } + } +} + type opAddSubTerm struct { Operator mathOp `parser:"@OpAddSub"` Term *addSubTerm `parser:"@@"` @@ -375,6 +511,12 @@ func (m *opAddSubTerm) checkForCustomError() error { return m.Term.checkForCustomError() } +func (m *opAddSubTerm) accept(v grammarVisitor) { + if m.Term != nil { + m.Term.accept(v) + } +} + type mathExpression struct { Left *addSubTerm `parser:"@@"` Right []*opAddSubTerm `parser:"@@*"` @@ -394,6 +536,19 @@ func (m *mathExpression) checkForCustomError() error { return nil } +func (m *mathExpression) accept(v grammarVisitor) { + if m.Left != nil { + m.Left.accept(v) + } + if m.Right != nil { + for _, r := range m.Right { + if r != nil { + r.accept(v) + } + } + } +} + type mathOp int const ( @@ -464,3 +619,11 @@ func buildLexer() *lexer.StatefulDefinition { {Name: "whitespace", Pattern: `\s+`}, }) } + +// grammarVisitor allows accessing the grammar AST nodes using the visitor pattern. +type grammarVisitor interface { + visitPath(v *path) + visitEditor(v *editor) + visitValue(v *value) + visitMathExprLiteral(v *mathExprLiteral) +} diff --git a/pkg/ottl/parser_test.go b/pkg/ottl/parser_test.go index d0a5e4b47add..8a6040741f63 100644 --- a/pkg/ottl/parser_test.go +++ b/pkg/ottl/parser_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/alecthomas/participle/v2/lexer" "github.com/stretchr/testify/assert" "go.opentelemetry.io/collector/component/componenttest" @@ -207,6 +208,11 @@ func Test_parse(t *testing.T) { Value: &value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 38, + Line: 1, + Column: 39, + }, Context: "bear", Fields: []field{ { @@ -267,6 +273,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 24, + Line: 1, + Column: 25, + }, Context: "bear", Fields: []field{ { @@ -298,6 +309,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, Context: "foo", Fields: []field{ { @@ -337,6 +353,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, Context: "", Fields: []field{ { @@ -373,6 +394,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 16, + Line: 1, + Column: 17, + }, Fields: []field{ { Name: "attributes", @@ -396,6 +422,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 51, + Line: 1, + Column: 52, + }, Fields: []field{ { Name: "attributes", @@ -431,6 +462,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 16, + Line: 1, + Column: 17, + }, Fields: []field{ { Name: "attributes", @@ -464,6 +500,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 16, + Line: 1, + Column: 17, + }, Fields: []field{ { Name: "attributes", @@ -499,6 +540,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, Context: "foo", Fields: []field{ { @@ -553,6 +599,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, Context: "foo", Fields: []field{ { @@ -585,6 +636,11 @@ func Test_parse(t *testing.T) { Left: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 44, + Line: 1, + Column: 45, + }, Fields: []field{ { Name: "name", @@ -614,6 +670,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, Context: "foo", Fields: []field{ { @@ -646,6 +707,11 @@ func Test_parse(t *testing.T) { Left: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 44, + Line: 1, + Column: 45, + }, Fields: []field{ { Name: "name", @@ -675,6 +741,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 7, + Line: 1, + Column: 8, + }, Context: "foo", Fields: []field{ { @@ -707,6 +778,11 @@ func Test_parse(t *testing.T) { Left: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 52, + Line: 1, + Column: 53, + }, Fields: []field{ { Name: "name", @@ -797,6 +873,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, Fields: []field{ { Name: "attributes", @@ -832,6 +913,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, Fields: []field{ { Name: "attributes", @@ -867,6 +953,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, Fields: []field{ { Name: "attributes", @@ -902,6 +993,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, Fields: []field{ { Name: "attributes", @@ -939,6 +1035,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, Fields: []field{ { Name: "attributes", @@ -980,6 +1081,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, Fields: []field{ { Name: "attributes", @@ -1024,6 +1130,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, Fields: []field{ { Name: "attributes", @@ -1095,6 +1206,11 @@ func Test_parse(t *testing.T) { { Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 70, + Line: 1, + Column: 71, + }, Fields: []field{ { Name: "attributes", @@ -1128,6 +1244,11 @@ func Test_parse(t *testing.T) { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, Fields: []field{ { Name: "attributes", @@ -1213,6 +1334,11 @@ func Test_parse(t *testing.T) { Left: &mathValue{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 55, + Line: 1, + Column: 56, + }, Fields: []field{ { Name: "three", @@ -1287,6 +1413,11 @@ func Test_parseCondition_full(t *testing.T) { Left: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 0, + Line: 1, + Column: 1, + }, Fields: []field{ { Name: "name", @@ -1314,6 +1445,11 @@ func Test_parseCondition_full(t *testing.T) { Left: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 0, + Line: 1, + Column: 1, + }, Fields: []field{ { Name: "name", @@ -1378,6 +1514,11 @@ func Test_parseCondition_full(t *testing.T) { Left: &mathValue{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 13, + Line: 1, + Column: 14, + }, Fields: []field{ { Name: "three", @@ -1477,6 +1618,11 @@ func setNameTest(b *booleanExpression) *parsedStatement { Value: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, Fields: []field{ { Name: "name", @@ -1713,6 +1859,11 @@ func Test_parseWhere(t *testing.T) { Left: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 24, + Line: 1, + Column: 25, + }, Fields: []field{ { Name: "name", @@ -1735,6 +1886,11 @@ func Test_parseWhere(t *testing.T) { Left: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 42, + Line: 1, + Column: 43, + }, Fields: []field{ { Name: "name", @@ -1763,6 +1919,11 @@ func Test_parseWhere(t *testing.T) { Left: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 24, + Line: 1, + Column: 25, + }, Fields: []field{ { Name: "name", @@ -1787,6 +1948,11 @@ func Test_parseWhere(t *testing.T) { Left: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 41, + Line: 1, + Column: 42, + }, Fields: []field{ { Name: "name", @@ -1839,6 +2005,11 @@ func Test_parseWhere(t *testing.T) { Left: value{ Literal: &mathExprLiteral{ Path: &path{ + Pos: lexer.Position{ + Offset: 28, + Line: 1, + Column: 29, + }, Fields: []field{ { Name: "name", diff --git a/pkg/ottl/paths.go b/pkg/ottl/paths.go new file mode 100644 index 000000000000..dbb66ee7c994 --- /dev/null +++ b/pkg/ottl/paths.go @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package ottl // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" + +// grammarPathVisitor is used to extract all path from a parsedStatement or booleanExpression +type grammarPathVisitor struct { + paths []path +} + +func (v *grammarPathVisitor) visitEditor(_ *editor) {} +func (v *grammarPathVisitor) visitValue(_ *value) {} +func (v *grammarPathVisitor) visitMathExprLiteral(_ *mathExprLiteral) {} + +func (v *grammarPathVisitor) visitPath(value *path) { + v.paths = append(v.paths, *value) +} + +func getParsedStatementPaths(ps *parsedStatement) []path { + visitor := &grammarPathVisitor{} + ps.Editor.accept(visitor) + if ps.WhereClause != nil { + ps.WhereClause.accept(visitor) + } + return visitor.paths +} + +func getBooleanExpressionPaths(be *booleanExpression) []path { + visitor := &grammarPathVisitor{} + be.accept(visitor) + return visitor.paths +} diff --git a/pkg/ottl/paths_test.go b/pkg/ottl/paths_test.go new file mode 100644 index 000000000000..9f31dda15718 --- /dev/null +++ b/pkg/ottl/paths_test.go @@ -0,0 +1,450 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package ottl + +import ( + "testing" + + "github.com/alecthomas/participle/v2/lexer" + "github.com/stretchr/testify/require" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottltest" +) + +func Test_getParsedStatementPaths(t *testing.T) { + tests := []struct { + name string + statement string + expected []path + }{ + { + name: "editor with nested map with path", + statement: `fff({"mapAttr": {"foo": "bar", "get": bear.honey, "arrayAttr":["foo", "bar"]}})`, + expected: []path{ + { + Pos: lexer.Position{ + Offset: 38, + Line: 1, + Column: 39, + }, + Context: "bear", + Fields: []field{ + { + Name: "honey", + }, + }, + }, + }, + }, + { + name: "editor with function path parameter", + statement: `set("foo", GetSomething(bear.honey))`, + expected: []path{ + { + Pos: lexer.Position{ + Offset: 24, + Line: 1, + Column: 25, + }, + Context: "bear", + Fields: []field{ + { + Name: "honey", + }, + }, + }, + }, + }, + { + name: "path with key", + statement: `set(foo.attributes["bar"].cat, "dog")`, + expected: []path{ + { + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, + Context: "foo", + Fields: []field{ + { + Name: "attributes", + Keys: []key{ + { + String: ottltest.Strp("bar"), + }, + }, + }, + { + Name: "cat", + }, + }, + }, + }, + }, + { + name: "single path field segment", + statement: `set(attributes["bar"], "dog")`, + expected: []path{ + { + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, + Context: "", + Fields: []field{ + { + Name: "attributes", + Keys: []key{ + { + String: ottltest.Strp("bar"), + }, + }, + }, + }, + }, + }, + }, + { + name: "converter parameters", + statement: `replace_pattern(attributes["message"], "device=*", attributes["device_name"], SHA256)`, + expected: []path{ + { + Pos: lexer.Position{ + Offset: 16, + Line: 1, + Column: 17, + }, + Fields: []field{ + { + Name: "attributes", + Keys: []key{ + { + String: ottltest.Strp("message"), + }, + }, + }, + }, + }, + { + Pos: lexer.Position{ + Offset: 51, + Line: 1, + Column: 52, + }, + Fields: []field{ + { + Name: "attributes", + Keys: []key{ + { + String: ottltest.Strp("device_name"), + }, + }, + }, + }, + }, + }, + }, + { + name: "complex path with multiple keys", + statement: `set(foo.bar["x"]["y"].z, Test()[0]["pass"])`, + expected: []path{ + { + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, + Context: "foo", + Fields: []field{ + { + Name: "bar", + Keys: []key{ + { + String: ottltest.Strp("x"), + }, + { + String: ottltest.Strp("y"), + }, + }, + }, + { + Name: "z", + }, + }, + }, + }, + }, + { + name: "where clause", + statement: `set(foo.attributes["bar"].cat, "dog") where name == "fido"`, + expected: []path{ + { + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, + Context: "foo", + Fields: []field{ + { + Name: "attributes", + Keys: []key{ + { + String: ottltest.Strp("bar"), + }, + }, + }, + { + Name: "cat", + }, + }, + }, + { + Pos: lexer.Position{ + Offset: 44, + Line: 1, + Column: 45, + }, + Fields: []field{ + { + Name: "name", + }, + }, + }, + }, + }, + { + name: "where clause multiple conditions", + statement: `set(foo.attributes["bar"].cat, "dog") where name == "fido" and surname == "dido" or surname == "DIDO"`, + expected: []path{ + { + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, + Context: "foo", + Fields: []field{ + { + Name: "attributes", + Keys: []key{ + { + String: ottltest.Strp("bar"), + }, + }, + }, + { + Name: "cat", + }, + }, + }, + { + Pos: lexer.Position{ + Offset: 44, + Line: 1, + Column: 45, + }, + Fields: []field{ + { + Name: "name", + }, + }, + }, + { + Pos: lexer.Position{ + Offset: 63, + Line: 1, + Column: 64, + }, + Fields: []field{ + { + Name: "surname", + }, + }, + }, + { + Pos: lexer.Position{ + Offset: 84, + Line: 1, + Column: 85, + }, + Fields: []field{ + { + Name: "surname", + }, + }, + }, + }, + }, + { + name: "where clause sub expression", + statement: `set(foo.attributes["bar"].cat, "value") where three / (1 + 1) == foo.value`, + expected: []path{ + { + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, + Context: "foo", + Fields: []field{ + { + Name: "attributes", + Keys: []key{ + { + String: ottltest.Strp("bar"), + }, + }, + }, + { + Name: "cat", + }, + }, + }, + { + Pos: lexer.Position{ + Offset: 46, + Line: 1, + Column: 47, + }, + Fields: []field{ + { + Name: "three", + }, + }, + }, + { + Pos: lexer.Position{ + Offset: 65, + Line: 1, + Column: 66, + }, + Context: "foo", + Fields: []field{ + { + Name: "value", + }, + }, + }, + }, + }, + { + name: "converter with path list", + statement: `set(attributes["test"], [bear.bear, bear.honey])`, + expected: []path{ + { + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, + Fields: []field{ + { + Name: "attributes", + Keys: []key{ + { + String: ottltest.Strp("test"), + }, + }, + }, + }, + }, + { + Pos: lexer.Position{ + Offset: 25, + Line: 1, + Column: 26, + }, + Context: "bear", + Fields: []field{{Name: "bear"}}, + }, + { + Pos: lexer.Position{ + Offset: 36, + Line: 1, + Column: 37, + }, + Context: "bear", + Fields: []field{{Name: "honey"}}, + }, + }, + }, + { + name: "converter math math expression", + statement: `set(attributes["test"], 1000 - 600) where 1 + 1 * 2 == three / One()`, + expected: []path{ + { + Pos: lexer.Position{ + Offset: 4, + Line: 1, + Column: 5, + }, + Fields: []field{ + { + Name: "attributes", + Keys: []key{ + { + String: ottltest.Strp("test"), + }, + }, + }, + }, + }, + { + Pos: lexer.Position{ + Offset: 55, + Line: 1, + Column: 56, + }, + Fields: []field{ + { + Name: "three", + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ps, err := parseStatement(tt.statement) + require.NoError(t, err) + + paths := getParsedStatementPaths(ps) + require.Equal(t, tt.expected, paths) + }) + } +} + +func Test_getBooleanExpressionPaths(t *testing.T) { + expected := []path{ + { + Pos: lexer.Position{ + Offset: 0, + Line: 1, + Column: 1, + }, + Context: "honey", + Fields: []field{{Name: "bear"}}, + }, + { + Pos: lexer.Position{ + Offset: 21, + Line: 1, + Column: 22, + }, + Context: "foo", + Fields: []field{{Name: "bar"}}, + }, + } + + c, err := parseCondition("honey.bear == 1 and (foo.bar == true or 1 == 1)") + require.NoError(t, err) + + paths := getBooleanExpressionPaths(c) + require.Equal(t, expected, paths) +}