Skip to content

Commit

Permalink
[pkg/ottl] Support dynamic indexing (#36719)
Browse files Browse the repository at this point in the history
  • Loading branch information
odubajDT authored Jan 9, 2025
1 parent 0387783 commit 1629eda
Show file tree
Hide file tree
Showing 13 changed files with 441 additions and 53 deletions.
27 changes: 27 additions & 0 deletions .chloggen/indexing-pkg-ottl.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: breaking

# 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: "Support dynamic indexing of maps and slices."

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

# (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: []
31 changes: 29 additions & 2 deletions pkg/ottl/contexts/internal/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ func GetMapValue[K any](ctx context.Context, tCtx K, m pcommon.Map, keys []ottl.
return nil, err
}
if s == nil {
return nil, fmt.Errorf("non-string indexing is not supported")
resString, err := FetchValueFromExpression[K, string](ctx, tCtx, keys[0])
if err != nil {
return nil, fmt.Errorf("unable to resolve a string index in map: %w", err)
}
s = resString
}

val, ok := m.Get(*s)
Expand All @@ -43,7 +47,11 @@ func SetMapValue[K any](ctx context.Context, tCtx K, m pcommon.Map, keys []ottl.
return err
}
if s == nil {
return fmt.Errorf("non-string indexing is not supported")
resString, err := FetchValueFromExpression[K, string](ctx, tCtx, keys[0])
if err != nil {
return fmt.Errorf("unable to resolve a string index in map: %w", err)
}
s = resString
}

currentValue, ok := m.Get(*s)
Expand All @@ -52,3 +60,22 @@ func SetMapValue[K any](ctx context.Context, tCtx K, m pcommon.Map, keys []ottl.
}
return setIndexableValue[K](ctx, tCtx, currentValue, val, keys[1:])
}

func FetchValueFromExpression[K any, T int64 | string](ctx context.Context, tCtx K, key ottl.Key[K]) (*T, error) {
p, err := key.ExpressionGetter(ctx, tCtx)
if err != nil {
return nil, err
}
if p == nil {
return nil, fmt.Errorf("invalid key type")
}
res, err := p.Get(ctx, tCtx)
if err != nil {
return nil, err
}
resVal, ok := res.(T)
if !ok {
return nil, fmt.Errorf("could not resolve key for map/slice, expecting '%T' but got '%T'", resVal, res)
}
return &resVal, nil
}
54 changes: 46 additions & 8 deletions pkg/ottl/contexts/internal/map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ import (
)

func Test_GetMapValue_Invalid(t *testing.T) {
getSetter := &ottl.StandardGetSetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
return nil, nil
},
Setter: func(_ context.Context, _ any, _ any) error {
return nil
},
}
tests := []struct {
name string
keys []ottl.Key[any]
Expand All @@ -26,42 +34,49 @@ func Test_GetMapValue_Invalid(t *testing.T) {
keys: []ottl.Key[any]{
&TestKey[any]{
I: ottltest.Intp(0),
G: getSetter,
},
},
err: fmt.Errorf("non-string indexing is not supported"),
err: fmt.Errorf("unable to resolve a string index in map: could not resolve key for map/slice, expecting 'string' but got '<nil>'"),
},
{
name: "index map with int",
keys: []ottl.Key[any]{
&TestKey[any]{
S: ottltest.Strp("map"),
G: getSetter,
},
&TestKey[any]{
I: ottltest.Intp(0),
G: getSetter,
},
},
err: fmt.Errorf("map must be indexed by a string"),
err: fmt.Errorf("unable to resolve a string index in map: could not resolve key for map/slice, expecting 'string' but got '<nil>'"),
},
{
name: "index slice with string",
keys: []ottl.Key[any]{
&TestKey[any]{
S: ottltest.Strp("slice"),
G: getSetter,
},
&TestKey[any]{
S: ottltest.Strp("invalid"),
G: getSetter,
},
},
err: fmt.Errorf("slice must be indexed by an int"),
err: fmt.Errorf("unable to resolve an integer index in slice: could not resolve key for map/slice, expecting 'int64' but got '<nil>'"),
},
{
name: "index too large",
keys: []ottl.Key[any]{
&TestKey[any]{
S: ottltest.Strp("slice"),
G: getSetter,
},
&TestKey[any]{
I: ottltest.Intp(1),
G: getSetter,
},
},
err: fmt.Errorf("index 1 out of bounds"),
Expand All @@ -71,9 +86,11 @@ func Test_GetMapValue_Invalid(t *testing.T) {
keys: []ottl.Key[any]{
&TestKey[any]{
S: ottltest.Strp("slice"),
G: getSetter,
},
&TestKey[any]{
I: ottltest.Intp(-1),
G: getSetter,
},
},
err: fmt.Errorf("index -1 out of bounds"),
Expand All @@ -83,9 +100,11 @@ func Test_GetMapValue_Invalid(t *testing.T) {
keys: []ottl.Key[any]{
&TestKey[any]{
S: ottltest.Strp("string"),
G: getSetter,
},
&TestKey[any]{
S: ottltest.Strp("string"),
G: getSetter,
},
},
err: fmt.Errorf("type Str does not support string indexing"),
Expand All @@ -102,7 +121,7 @@ func Test_GetMapValue_Invalid(t *testing.T) {
s.AppendEmpty()

_, err := GetMapValue[any](context.Background(), nil, m, tt.keys)
assert.Equal(t, tt.err, err)
assert.Equal(t, tt.err.Error(), err.Error())
})
}
}
Expand All @@ -129,6 +148,14 @@ func Test_GetMapValue_NilKey(t *testing.T) {
}

func Test_SetMapValue_Invalid(t *testing.T) {
getSetter := &ottl.StandardGetSetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
return nil, nil
},
Setter: func(_ context.Context, _ any, _ any) error {
return nil
},
}
tests := []struct {
name string
keys []ottl.Key[any]
Expand All @@ -139,42 +166,49 @@ func Test_SetMapValue_Invalid(t *testing.T) {
keys: []ottl.Key[any]{
&TestKey[any]{
I: ottltest.Intp(0),
G: getSetter,
},
},
err: fmt.Errorf("non-string indexing is not supported"),
err: fmt.Errorf("unable to resolve a string index in map: could not resolve key for map/slice, expecting 'string' but got '<nil>'"),
},
{
name: "index map with int",
keys: []ottl.Key[any]{
&TestKey[any]{
S: ottltest.Strp("map"),
G: getSetter,
},
&TestKey[any]{
I: ottltest.Intp(0),
G: getSetter,
},
},
err: fmt.Errorf("map must be indexed by a string"),
err: fmt.Errorf("unable to resolve a string index in map: could not resolve key for map/slice, expecting 'string' but got '<nil>'"),
},
{
name: "index slice with string",
keys: []ottl.Key[any]{
&TestKey[any]{
S: ottltest.Strp("slice"),
G: getSetter,
},
&TestKey[any]{
S: ottltest.Strp("map"),
G: getSetter,
},
},
err: fmt.Errorf("slice must be indexed by an int"),
err: fmt.Errorf("unable to resolve an integer index in slice: could not resolve key for map/slice, expecting 'int64' but got '<nil>'"),
},
{
name: "slice index too large",
keys: []ottl.Key[any]{
&TestKey[any]{
S: ottltest.Strp("slice"),
G: getSetter,
},
&TestKey[any]{
I: ottltest.Intp(1),
G: getSetter,
},
},
err: fmt.Errorf("index 1 out of bounds"),
Expand All @@ -184,9 +218,11 @@ func Test_SetMapValue_Invalid(t *testing.T) {
keys: []ottl.Key[any]{
&TestKey[any]{
S: ottltest.Strp("slice"),
G: getSetter,
},
&TestKey[any]{
I: ottltest.Intp(-1),
G: getSetter,
},
},
err: fmt.Errorf("index -1 out of bounds"),
Expand All @@ -196,9 +232,11 @@ func Test_SetMapValue_Invalid(t *testing.T) {
keys: []ottl.Key[any]{
&TestKey[any]{
S: ottltest.Strp("string"),
G: getSetter,
},
&TestKey[any]{
S: ottltest.Strp("string"),
G: getSetter,
},
},
err: fmt.Errorf("type Str does not support string indexing"),
Expand All @@ -215,7 +253,7 @@ func Test_SetMapValue_Invalid(t *testing.T) {
s.AppendEmpty()

err := SetMapValue[any](context.Background(), nil, m, tt.keys, "value")
assert.Equal(t, tt.err, err)
assert.Equal(t, tt.err.Error(), err.Error())
})
}
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/ottl/contexts/internal/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ var _ ottl.Key[any] = &TestKey[any]{}
type TestKey[K any] struct {
S *string
I *int64
G ottl.Getter[K]
}

func (k *TestKey[K]) String(_ context.Context, _ K) (*string, error) {
Expand All @@ -55,3 +56,7 @@ func (k *TestKey[K]) String(_ context.Context, _ K) (*string, error) {
func (k *TestKey[K]) Int(_ context.Context, _ K) (*int64, error) {
return k.I, nil
}

func (k *TestKey[K]) ExpressionGetter(_ context.Context, _ K) (ottl.Getter[K], error) {
return k.G, nil
}
12 changes: 10 additions & 2 deletions pkg/ottl/contexts/internal/slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ func GetSliceValue[K any](ctx context.Context, tCtx K, s pcommon.Slice, keys []o
return nil, err
}
if i == nil {
return nil, fmt.Errorf("non-integer indexing is not supported")
resInt, err := FetchValueFromExpression[K, int64](ctx, tCtx, keys[0])
if err != nil {
return nil, fmt.Errorf("unable to resolve an integer index in slice: %w", err)
}
i = resInt
}

idx := int(*i)
Expand All @@ -44,7 +48,11 @@ func SetSliceValue[K any](ctx context.Context, tCtx K, s pcommon.Slice, keys []o
return err
}
if i == nil {
return fmt.Errorf("non-integer indexing is not supported")
resInt, err := FetchValueFromExpression[K, int64](ctx, tCtx, keys[0])
if err != nil {
return fmt.Errorf("unable to resolve an integer index in slice: %w", err)
}
i = resInt
}

idx := int(*i)
Expand Down
Loading

0 comments on commit 1629eda

Please sign in to comment.