Skip to content

Commit 733bcf4

Browse files
alexcamsedmocostaevan-bradley
authored
Add OTTL delete function (#44053)
Co-authored-by: Edmo Vamerlatti Costa <11836452+edmocosta@users.noreply.github.com> Co-authored-by: Evan Bradley <11745660+evan-bradley@users.noreply.github.com>
1 parent 9935f48 commit 733bcf4

File tree

6 files changed

+534
-0
lines changed

6 files changed

+534
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. receiver/filelog)
7+
component: pkg/ottl
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: "Introducing `delete_index` function for deleting items from an existing array"
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [43098]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: [user]

pkg/ottl/e2e/e2e_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ func Test_e2e_editors(t *testing.T) {
6363
tCtx.GetLogRecord().Attributes().Remove("things")
6464
tCtx.GetLogRecord().Attributes().Remove("conflict.conflict1")
6565
tCtx.GetLogRecord().Attributes().Remove("conflict")
66+
tCtx.GetLogRecord().Attributes().Remove("slice2")
6667
},
6768
},
6869
{
@@ -74,6 +75,7 @@ func Test_e2e_editors(t *testing.T) {
7475
tCtx.GetLogRecord().Attributes().Remove("things")
7576
tCtx.GetLogRecord().Attributes().Remove("conflict.conflict1")
7677
tCtx.GetLogRecord().Attributes().Remove("conflict")
78+
tCtx.GetLogRecord().Attributes().Remove("slice2")
7779
},
7880
},
7981
{
@@ -93,6 +95,12 @@ func Test_e2e_editors(t *testing.T) {
9395
tCtx.GetLogRecord().Attributes().PutInt("things.0.value", 2)
9496
tCtx.GetLogRecord().Attributes().PutStr("things.1.name", "bar")
9597
tCtx.GetLogRecord().Attributes().PutInt("things.1.value", 5)
98+
99+
tCtx.GetLogRecord().Attributes().Remove("slice2")
100+
tCtx.GetLogRecord().Attributes().PutStr("slice2.0", "val")
101+
tCtx.GetLogRecord().Attributes().PutStr("slice2.1", "foo")
102+
tCtx.GetLogRecord().Attributes().PutStr("slice2.2", "bar")
103+
tCtx.GetLogRecord().Attributes().PutStr("slice2.3", "baz")
96104
},
97105
},
98106
{
@@ -117,6 +125,11 @@ func Test_e2e_editors(t *testing.T) {
117125
m.PutInt("test.things.0.value", 2)
118126
m.PutStr("test.things.1.name", "bar")
119127
m.PutInt("test.things.1.value", 5)
128+
129+
m.PutStr("test.slice2.0", "val")
130+
m.PutStr("test.slice2.1", "foo")
131+
m.PutStr("test.slice2.2", "bar")
132+
m.PutStr("test.slice2.3", "baz")
120133
m.CopyTo(tCtx.GetLogRecord().Attributes())
121134
},
122135
},
@@ -145,6 +158,11 @@ func Test_e2e_editors(t *testing.T) {
145158
m.PutStr("test.things.1.name", "bar")
146159
m.PutInt("test.things.1.value", 5)
147160

161+
m.PutStr("test.slice2", "val")
162+
m.PutStr("test.slice2.0", "foo")
163+
m.PutStr("test.slice2.1", "bar")
164+
m.PutStr("test.slice2.2", "baz")
165+
148166
m.CopyTo(tCtx.GetLogRecord().Attributes())
149167
},
150168
},
@@ -161,6 +179,10 @@ func Test_e2e_editors(t *testing.T) {
161179
m.PutStr("foo.flags", "pass")
162180
m.PutStr("foo.bar", "pass")
163181
m.PutStr("foo.flags", "pass")
182+
m.PutStr("slice2.0", "val")
183+
m.PutStr("slice2.1", "foo")
184+
m.PutStr("slice2.2", "bar")
185+
m.PutStr("slice2.3", "baz")
164186
m.PutEmptySlice("foo.slice").AppendEmpty().SetStr("val")
165187
m.PutStr("conflict.conflict1.conflict2", "nopass")
166188
mm := m.PutEmptyMap("conflict.conflict1")
@@ -189,6 +211,7 @@ func Test_e2e_editors(t *testing.T) {
189211
tCtx.GetLogRecord().Attributes().Remove("things")
190212
tCtx.GetLogRecord().Attributes().Remove("conflict.conflict1")
191213
tCtx.GetLogRecord().Attributes().Remove("conflict")
214+
tCtx.GetLogRecord().Attributes().Remove("slice2")
192215
},
193216
},
194217
{
@@ -206,6 +229,7 @@ func Test_e2e_editors(t *testing.T) {
206229
tCtx.GetLogRecord().Attributes().Remove("things")
207230
tCtx.GetLogRecord().Attributes().Remove("conflict.conflict1")
208231
tCtx.GetLogRecord().Attributes().Remove("conflict")
232+
tCtx.GetLogRecord().Attributes().Remove("slice2")
209233
},
210234
},
211235
{
@@ -393,6 +417,46 @@ func Test_e2e_editors(t *testing.T) {
393417
s.AppendEmpty().SetInt(6)
394418
},
395419
},
420+
{
421+
statement: `delete_index(attributes["slice2"], 0)`,
422+
want: func(tCtx *ottllog.TransformContext) {
423+
v, _ := tCtx.GetLogRecord().Attributes().Get("slice2")
424+
s := v.Slice()
425+
s.RemoveIf(func(v pcommon.Value) bool {
426+
return v.Str() == "val"
427+
})
428+
},
429+
},
430+
{
431+
statement: `delete_index(attributes["slice2"], Len(attributes["slice2"]) - 1)`,
432+
want: func(tCtx *ottllog.TransformContext) {
433+
v, _ := tCtx.GetLogRecord().Attributes().Get("slice2")
434+
s := v.Slice()
435+
s.RemoveIf(func(v pcommon.Value) bool {
436+
return v.Str() == "baz"
437+
})
438+
},
439+
},
440+
{
441+
statement: `delete_index(attributes["slice2"], 1, endIndex=3)`,
442+
want: func(tCtx *ottllog.TransformContext) {
443+
v, _ := tCtx.GetLogRecord().Attributes().Get("slice2")
444+
s := v.Slice()
445+
s.RemoveIf(func(v pcommon.Value) bool {
446+
return (v.Str() == "foo" || v.Str() == "bar")
447+
})
448+
},
449+
},
450+
{
451+
statement: `delete_index(attributes["slice2"], Index(attributes["slice2"], "foo"))`,
452+
want: func(tCtx *ottllog.TransformContext) {
453+
v, _ := tCtx.GetLogRecord().Attributes().Get("slice2")
454+
s := v.Slice()
455+
s.RemoveIf(func(v pcommon.Value) bool {
456+
return v.Str() == "foo"
457+
})
458+
},
459+
},
396460
}
397461

398462
for _, tt := range tests {
@@ -2190,6 +2254,12 @@ func constructLogTransformContextEditors() *ottllog.TransformContext {
21902254
thing2.PutStr("name", "bar")
21912255
thing2.PutInt("value", 5)
21922256

2257+
s3 := logRecord.Attributes().PutEmptySlice("slice2")
2258+
s3.AppendEmpty().SetStr("val")
2259+
s3.AppendEmpty().SetStr("foo")
2260+
s3.AppendEmpty().SetStr("bar")
2261+
s3.AppendEmpty().SetStr("baz")
2262+
21932263
return ottllog.NewTransformContextPtr(rLogs, rLogs.ScopeLogs().At(0), logRecord)
21942264
}
21952265

pkg/ottl/ottlfuncs/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Editors:
4646
Available Editors:
4747

4848
- [append](#append)
49+
- [delete_index](#delete_index)
4950
- [delete_key](#delete_key)
5051
- [delete_matching_keys](#delete_matching_keys)
5152
- [keep_matching_keys](#keep_matching_keys)
@@ -73,6 +74,22 @@ Resulting field is always of type `pcommon.Slice` and will not convert the types
7374
- `append(log.attributes["tags"], values = ["staging", "staging:east"])`
7475
- `append(log.attributes["tags_copy"], log.attributes["tags"])`
7576

77+
### delete_index
78+
79+
`delete_index(target, startIndex, Optional[endIndex])`
80+
81+
The `delete_index` function removes elements from a slice. It deletes elements from `startIndex` up to, but not including, `endIndex`. If `endIndex` is not provided, only the element at `target[startIndex]` is deleted. If `startIndex` equals `endIndex`, no changes are applied to the target.
82+
83+
Examples:
84+
85+
- `delete_index(attributes["tags"], 0)` # deletes first
86+
87+
- `delete_index(attributes["tags"], Len(attributes["tags"]) - 1)` # deletes last
88+
89+
- `delete_index(attributes["tags"], 0, 3)` # deletes indices 0, 1, & 2
90+
91+
- `delete_index(attributes["tags"], Index(attributes["tags"], "unparsed"))` # deletes first occurrence of "unparsed"
92+
7693
### delete_key
7794

7895
`delete_key(target, key)`
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs"
5+
6+
import (
7+
"context"
8+
"errors"
9+
"fmt"
10+
11+
"go.opentelemetry.io/collector/pdata/pcommon"
12+
13+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
14+
)
15+
16+
type DeleteIndexArguments[K any] struct {
17+
Target ottl.PSliceGetSetter[K]
18+
StartIndex ottl.IntGetter[K]
19+
EndIndex ottl.Optional[ottl.IntGetter[K]]
20+
}
21+
22+
func NewDeleteIndexFactory[K any]() ottl.Factory[K] {
23+
return ottl.NewFactory("delete_index", &DeleteIndexArguments[K]{}, createDeleteIndexFunction[K])
24+
}
25+
26+
func createDeleteIndexFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) {
27+
args, ok := oArgs.(*DeleteIndexArguments[K])
28+
if !ok {
29+
return nil, errors.New("DeleteIndexFactory args must be of type *DeleteIndexArguments[K]")
30+
}
31+
32+
return deleteIndexFrom(args.Target, args.StartIndex, args.EndIndex), nil
33+
}
34+
35+
func deleteIndexFrom[K any](target ottl.PSliceGetSetter[K], startIndexGetter ottl.IntGetter[K], endIndexGetter ottl.Optional[ottl.IntGetter[K]]) ottl.ExprFunc[K] {
36+
return func(ctx context.Context, tCtx K) (any, error) {
37+
t, err := target.Get(ctx, tCtx)
38+
if err != nil {
39+
return nil, err
40+
}
41+
42+
startIndex, err := startIndexGetter.Get(ctx, tCtx)
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
sliceLen := int64(t.Len())
48+
endIndex := startIndex + 1
49+
if !endIndexGetter.IsEmpty() {
50+
endIndex, err = endIndexGetter.Get().Get(ctx, tCtx)
51+
if err != nil {
52+
return nil, err
53+
}
54+
}
55+
56+
if startIndex == 0 && endIndex == sliceLen {
57+
// If deleting all elements, return an empty slice without looping
58+
return nil, target.Set(ctx, tCtx, pcommon.NewSlice())
59+
}
60+
61+
err = validateBounds(startIndex, endIndex, sliceLen)
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
if startIndex == endIndex {
67+
// No elements to delete
68+
return nil, target.Set(ctx, tCtx, t)
69+
}
70+
71+
var i int64
72+
t.RemoveIf(func(_ pcommon.Value) bool {
73+
remove := i >= startIndex && i < endIndex
74+
i++
75+
return remove
76+
})
77+
return nil, target.Set(ctx, tCtx, t)
78+
}
79+
}
80+
81+
func validateBounds(startIndex, endIndex, sliceLen int64) error {
82+
if startIndex < 0 || startIndex >= sliceLen {
83+
return fmt.Errorf("startIndex %d out of bounds for slice of length %d", startIndex, sliceLen)
84+
}
85+
86+
if endIndex < startIndex {
87+
return fmt.Errorf("endIndex %d cannot be less than startIndex %d", endIndex, startIndex)
88+
}
89+
90+
if endIndex > sliceLen {
91+
return fmt.Errorf("deletion range [%d:%d] out of bounds for slice of length %d", startIndex, endIndex, sliceLen)
92+
}
93+
return nil
94+
}

0 commit comments

Comments
 (0)