Skip to content

Commit

Permalink
Add contains function
Browse files Browse the repository at this point in the history
  • Loading branch information
lkwronski committed Sep 18, 2024
1 parent f6aa99f commit 417027d
Show file tree
Hide file tree
Showing 10 changed files with 522 additions and 15 deletions.
27 changes: 27 additions & 0 deletions .chloggen/lkwronski.issue-30420-contains.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: pkg/ottl

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Add new Contains function to check item in a slice.

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [30420]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [user]
27 changes: 27 additions & 0 deletions .chloggen/lkwronski.issue-30420-p-slice-getter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: pkg/ottl

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Add `PSliceGetter`, a typed getter for `pcommon.Slice`

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [30420]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [api]
70 changes: 55 additions & 15 deletions pkg/ottl/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package e2e

import (
"context"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -869,71 +870,108 @@ func Test_e2e_converters(t *testing.T) {
func Test_e2e_ottl_features(t *testing.T) {
tests := []struct {
name string
statement string
statement []string
want func(tCtx ottllog.TransformContext)
}{
{
name: "where clause",
statement: `set(attributes["test"], "pass") where body == "operationB"`,
statement: []string{`set(attributes["test"], "pass") where body == "operationB"`},
want: func(_ ottllog.TransformContext) {},
},
{
name: "reach upwards",
statement: `set(attributes["test"], "pass") where resource.attributes["host.name"] == "localhost"`,
statement: []string{`set(attributes["test"], "pass") where resource.attributes["host.name"] == "localhost"`},
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().PutStr("test", "pass")
},
},
{
name: "Using enums",
statement: `set(severity_number, SEVERITY_NUMBER_TRACE2) where severity_number == SEVERITY_NUMBER_TRACE`,
statement: []string{`set(severity_number, SEVERITY_NUMBER_TRACE2) where severity_number == SEVERITY_NUMBER_TRACE`},
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().SetSeverityNumber(2)
},
},
{
name: "Using hex",
statement: `set(attributes["test"], "pass") where trace_id == TraceID(0x0102030405060708090a0b0c0d0e0f10)`,
statement: []string{`set(attributes["test"], "pass") where trace_id == TraceID(0x0102030405060708090a0b0c0d0e0f10)`},
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().PutStr("test", "pass")
},
},
{
name: "where clause without comparator",
statement: `set(attributes["test"], "pass") where IsMatch(body, "operation[AC]")`,
statement: []string{`set(attributes["test"], "pass") where IsMatch(body, "operation[AC]")`},
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().PutStr("test", "pass")
},
},
{
name: "where clause with Converter return value",
statement: `set(attributes["test"], "pass") where body == Concat(["operation", "A"], "")`,
statement: []string{`set(attributes["test"], "pass") where body == Concat(["operation", "A"], "")`},
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().PutStr("test", "pass")
},
},
{
name: "composing functions",
statement: `merge_maps(attributes, ParseJSON("{\"json_test\":\"pass\"}"), "insert") where body == "operationA"`,
statement: []string{`merge_maps(attributes, ParseJSON("{\"json_test\":\"pass\"}"), "insert") where body == "operationA"`},
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().PutStr("json_test", "pass")
},
},
{
name: "where clause with Contains return value",
statement: []string{`set(attributes["test"], "pass") where Contains(["hello", "world"], "hello")`},
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().PutStr("test", "pass")
},
},
{
name: "where clause with Contains ints return value",
statement: []string{`set(attributes["test"], "pass") where Contains([1, 2, 3, 4], 4)`},
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().PutStr("test", "pass")
},
},
{
name: "where clause with Contains floats return value",
statement: []string{`set(attributes["test"], "pass") where Contains([1.1, 2.2, 3.3, 4.4], 4.4)`},
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().PutStr("test", "pass")
},
},
{
name: `set attribute when tag "staging" is in tags attributes slice using Contains`,
statement: []string{
`set(attributes["tags"], ["staging", "hello", "world", "work"])`,
`set(attributes["staging"], "true") where Contains(attributes["tags"], "staging")`,
},
want: func(tCtx ottllog.TransformContext) {
var tags = tCtx.GetLogRecord().Attributes().PutEmptySlice("tags")
tags.AppendEmpty().SetStr("staging")
tags.AppendEmpty().SetStr("hello")
tags.AppendEmpty().SetStr("world")
tags.AppendEmpty().SetStr("work")

tCtx.GetLogRecord().Attributes().PutStr("staging", "true")
},
},
{
name: "complex indexing found",
statement: `set(attributes["test"], attributes["foo"]["bar"])`,
statement: []string{`set(attributes["test"], attributes["foo"]["bar"])`},
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().PutStr("test", "pass")
},
},
{
name: "complex indexing not found",
statement: `set(attributes["test"], attributes["metadata"]["uid"])`,
statement: []string{`set(attributes["test"], attributes["metadata"]["uid"])`},
want: func(_ ottllog.TransformContext) {},
},
{
name: "map value",
statement: `set(body, {"_raw": body, "test": {"result": attributes["foo"]["bar"], "time": UnixNano(time)}})`,
statement: []string{`set(body, {"_raw": body, "test": {"result": attributes["foo"]["bar"], "time": UnixNano(time)}})`},
want: func(tCtx ottllog.TransformContext) {
originalBody := tCtx.GetLogRecord().Body().AsString()
mapValue := tCtx.GetLogRecord().Body().SetEmptyMap()
Expand All @@ -945,23 +983,25 @@ func Test_e2e_ottl_features(t *testing.T) {
},
{
name: "map value as input to function",
statement: `set(attributes["isMap"], IsMap({"foo": {"bar": "baz", "test": "pass"}}))`,
statement: []string{`set(attributes["isMap"], IsMap({"foo": {"bar": "baz", "test": "pass"}}))`},
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().PutBool("isMap", true)
},
},
}

for _, tt := range tests {
t.Run(tt.statement, func(t *testing.T) {
t.Run(strings.Join(tt.statement, "\n"), func(t *testing.T) {
settings := componenttest.NewNopTelemetrySettings()
logParser, err := ottllog.NewParser(ottlfuncs.StandardFuncs[ottllog.TransformContext](), settings)
assert.NoError(t, err)
logStatements, err := logParser.ParseStatement(tt.statement)
logStatements, err := logParser.ParseStatements(tt.statement)
assert.NoError(t, err)

tCtx := constructLogTransformContext()
_, _, _ = logStatements.Execute(context.Background(), tCtx)
for i := range logStatements {
_, _, _ = logStatements[i].Execute(context.Background(), tCtx)
}

exTCtx := constructLogTransformContext()
tt.want(exTCtx)
Expand Down
41 changes: 41 additions & 0 deletions pkg/ottl/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,47 @@ func (m *mapGetter[K]) Get(ctx context.Context, tCtx K) (any, error) {
return result, nil
}

// PSliceGetter is a Getter that must return a pcommon.Slice.
type PSliceGetter[K any] interface {
Get(ctx context.Context, tCtx K) (pcommon.Slice, error)
}

// PStandardSliceGetter is a basic implementation of PSliceGetter
type StandardPSliceGetter[K any] struct {
Getter func(ctx context.Context, tCtx K) (any, error)
}

// Get retrieves a pcommon.Slice value.
// If the value is not a pcommon.Slice a new TypeError is returned.
// If there is an error getting the value it will be returned.
func (g StandardPSliceGetter[K]) Get(ctx context.Context, tCtx K) (pcommon.Slice, error) {
val, err := g.Getter(ctx, tCtx)
if err != nil {
return pcommon.Slice{}, fmt.Errorf("error getting value in %T: %w", g, err)
}
if val == nil {
return pcommon.Slice{}, TypeError("expected pcommon.Slice but got nil")
}
switch v := val.(type) {
case pcommon.Slice:
return v, nil
case pcommon.Value:
if v.Type() == pcommon.ValueTypeSlice {
return v.Slice(), nil
}
return pcommon.Slice{}, TypeError(fmt.Sprintf("expected pcommon.Slice but got %v", v.Type()))
case []any:
s := pcommon.NewSlice()
err := s.FromRaw(v)
if err != nil {
return pcommon.Slice{}, err
}
return s, nil
default:
return pcommon.Slice{}, TypeError(fmt.Sprintf("expected pcommon.Slice but got %T", val))
}
}

// TypeError represents that a value was not an expected type.
type TypeError string

Expand Down
12 changes: 12 additions & 0 deletions pkg/ottl/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,12 @@ func (p *Parser[K]) buildSliceArg(argVal value, argType reflect.Type) (any, erro
return nil, err
}
return arg, nil
case strings.HasPrefix(name, "PSliceGetter"):
arg, err := buildSlice[PSliceGetter[K]](argVal, argType, p.buildArg, name)
if err != nil {
return nil, err
}
return arg, nil
case strings.HasPrefix(name, "StringGetter"):
arg, err := buildSlice[StringGetter[K]](argVal, argType, p.buildArg, name)
if err != nil {
Expand Down Expand Up @@ -533,6 +539,12 @@ func (p *Parser[K]) buildArg(argVal value, argType reflect.Type) (any, error) {
return nil, err
}
return StandardPMapGetter[K]{Getter: arg.Get}, nil
case strings.HasPrefix(name, "PSliceGetter"):
arg, err := p.newGetter(argVal)
if err != nil {
return nil, err
}
return StandardPSliceGetter[K]{Getter: arg.Get}, nil
case strings.HasPrefix(name, "DurationGetter"):
arg, err := p.newGetter(argVal)
if err != nil {
Expand Down
Loading

0 comments on commit 417027d

Please sign in to comment.