diff --git a/pkg/ottl/ottlfuncs/README.md b/pkg/ottl/ottlfuncs/README.md index 26cf0d09a468..020d879c722b 100644 --- a/pkg/ottl/ottlfuncs/README.md +++ b/pkg/ottl/ottlfuncs/README.md @@ -247,32 +247,31 @@ Examples: ### rename -`rename(map, field, targetField, Optional[ignore_missing], Optional[conflict_strategy] )` +`rename(target, source_map, source_key, [Optional] ignore_missing = true, [Optional] conflict_strategy = upsert)` -The `rename` function renames the `map` `field` to `targetField`. -`map` is a path expression to a `pcommon.Map` type field. `field` is a string value of the `map` key that is being renamed. `targetField` is a string value that the `field` is renamed to. +The `rename` function combines `set` and `delete_key` calls in one function. It creates the `target` field with the `source_key` value from `source_map` and it deletes the `source_key` from `source_map`. -This combines what previously required two calls of `set` following with `delete_key` in one function call. +`target` is a path expression to a telemetry field. `source_map` is a path expression to a `pcommon.Map` type field. `source_key` is a string key for `source_map`. How the ```rename``` function behaves is controlled by the optional `ignore_missing` and `conflict_strategy` arguments. -`ignore_missing` is optional boolean argument, that specifies what happens when the `field` is missing from the `map`. It is set to `true` by default. -- The `true` value results in no changes to the `map` if the `field` key doesn't exists in the map -- The `false` value results in error if the `field` key doesn't exists in the map +`ignore_missing` is optional boolean argument that specifies what happens when the `source_key` is missing from the `source_map`. It is set to `true` by default. +- The `true` value results in no changes if the `source_key` key doesn't exists in the `source_map` +- The `false` value results in error if the `source_key` key doesn't exists in the `source_map` -`conflict_strategy` is an optional string paramater that specifies the conflict resolution strategy for the `targetField`. -Valid values are `replace`, `fail`, and `ignore`. By default, it is set to `replace`. -- The `replace` overwrites the `targetField` if it is already present. -- The `fail` returns an error if `targetField` is already present. -- The `ignore` results in no changes to the `map` if `targetField` is already present. +`conflict_strategy` is an optional string parameter that specifies the conflict resolution strategy for the `target`. +Valid values are `upsert`, `fail`, and `insert`. By default, it is set to `upsert`. +- The `upsert` overwrites the `target` value if it is already present. +- The `fail` returns an error if `target` is already present. +- The `insert` results in no changes if `target` is already present. Examples: -- `rename(attributes, "foo", "bar")` -- `rename(attributes, "foo", "bar", false)` -- `rename(attributes, "foo", "bar", true, "ignore")` +- `rename(attributes["destination"], attributes, "source")` +- `rename(attributes["destination"], attributes, "source", false)` +- `rename(attributes["destination"], attributes, "source", true, "insert")` ### replace_all_matches diff --git a/pkg/ottl/ottlfuncs/func_rename.go b/pkg/ottl/ottlfuncs/func_rename.go index bda82a139610..f8280fa61b5d 100644 --- a/pkg/ottl/ottlfuncs/func_rename.go +++ b/pkg/ottl/ottlfuncs/func_rename.go @@ -18,16 +18,17 @@ var ( ) const ( - renameConflictReplace = "replace" - renameConflictFail = "fail" - renameConflictIgnore = "ignore" + renameConflictInsert = "insert" + renameConflictUpsert = "upsert" + renameConflictFail = "fail" ) -// rename(map, field, target_field, [Optional] ignore_missing = true, [Optional] conflict_strategy = replace) +// rename(target, source_map, source_key, [Optional] ignore_missing = true, [Optional] conflict_strategy = upsert) type RenameArguments[K any] struct { - Map ottl.PMapGetter[K] - Field string - TargetField string + Target ottl.GetSetter[K] + SourceMap ottl.PMapGetter[K] + SourceKey string + IgnoreMissing ottl.Optional[bool] ConflictStrategy ottl.Optional[string] } @@ -43,23 +44,24 @@ func createRenameFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) ( return nil, fmt.Errorf("RenameFactory args must be of type *RenameArguments[K]") } - return rename(args.Map, args.Field, args.TargetField, args.IgnoreMissing, args.ConflictStrategy) + return rename(args.Target, args.SourceMap, args.SourceKey, args.IgnoreMissing, args.ConflictStrategy) } -func rename[K any](mg ottl.PMapGetter[K], f string, tf string, im ottl.Optional[bool], cs ottl.Optional[string]) (ottl.ExprFunc[K], error) { - conflictStrategy := renameConflictReplace +func rename[K any](target ottl.GetSetter[K], sm ottl.PMapGetter[K], sourceKey string, + im ottl.Optional[bool], cs ottl.Optional[string]) (ottl.ExprFunc[K], error) { + conflictStrategy := renameConflictUpsert if !cs.IsEmpty() { conflictStrategy = cs.Get() } - if conflictStrategy != renameConflictReplace && - conflictStrategy != renameConflictFail && - conflictStrategy != renameConflictIgnore { - return nil, fmt.Errorf("%v %w, must be %q, %q or %q", conflictStrategy, ErrRenameInvalidConflictStrategy, renameConflictReplace, renameConflictFail, renameConflictIgnore) + if conflictStrategy != renameConflictInsert && + conflictStrategy != renameConflictUpsert && + conflictStrategy != renameConflictFail { + return nil, fmt.Errorf("%v %w, must be %q, %q or %q", conflictStrategy, ErrRenameInvalidConflictStrategy, renameConflictInsert, renameConflictFail, renameConflictUpsert) } return func(ctx context.Context, tCtx K) (any, error) { - m, err := mg.Get(ctx, tCtx) + sourceMap, err := sm.Get(ctx, tCtx) if err != nil { return nil, err } @@ -70,45 +72,50 @@ func rename[K any](mg ottl.PMapGetter[K], f string, tf string, im ottl.Optional[ ignoreMissing = im.Get() } - val, exists := m.Get(f) + // Get value from source_map + sourceVal, sourceExists := sourceMap.Get(sourceKey) // Apply ignore_missing to the source - if !exists { + if !sourceExists { if ignoreMissing { - // If ignore missing return + // If ignore_missing is true, return return nil, nil } - return nil, fmt.Errorf("%v %w, while ignore_missing is false", f, ErrRenameKeyIsMissing) + return nil, fmt.Errorf("%v %w, while ignore_missing is false", sourceKey, ErrRenameKeyIsMissing) } // Apply conflict_strategy to the target - _, oldExists := m.Get(tf) + oldVal, err := target.Get(ctx, tCtx) + if err != nil { + return nil, err + } + + oldExists := (oldVal != nil) switch conflictStrategy { - case renameConflictReplace: + case renameConflictInsert: + // Noop if target field present + if oldExists { + return nil, nil + } + case renameConflictUpsert: // Overwrite if present or create when missing case renameConflictFail: // Fail if target field present if oldExists { return nil, ErrRenameKeyAlreadyExists } - case renameConflictIgnore: - // Noop if target field present - if oldExists { - return nil, nil - } } - // If field and targetField are the same - if f == tf { - return nil, nil - } + // Save raw value, since the sourceVal gets modified when the key is removed + rawVal := sourceVal.AsRaw() + + // Remove field from source + sourceMap.Remove(sourceKey) - // Copy field value to targetField - val.CopyTo(m.PutEmpty(tf)) + // Set value to target + target.Set(ctx, tCtx, rawVal) - // Remove field from map - m.Remove(f) return nil, nil }, nil } diff --git a/pkg/ottl/ottlfuncs/func_rename_test.go b/pkg/ottl/ottlfuncs/func_rename_test.go index 4f3994056d0e..99c453a2807d 100644 --- a/pkg/ottl/ottlfuncs/func_rename_test.go +++ b/pkg/ottl/ottlfuncs/func_rename_test.go @@ -14,187 +14,211 @@ import ( ) func Test_rename(t *testing.T) { + inputMap := map[string]any{ + "test": "hello world", + "test2": int64(3), + "empty": nil, + } + input := pcommon.NewMap() - input.PutStr("test", "hello world") - input.PutInt("test2", 3) - input.PutEmpty("empty") + err := input.FromRaw(inputMap) + if err != nil { + t.Fatal(err) + } + + target := func(key string) *ottl.StandardGetSetter[pcommon.Value] { + return &ottl.StandardGetSetter[pcommon.Value]{ + Setter: func(ctx context.Context, tCtx pcommon.Value, val any) error { + if val == nil { + tCtx.Map().PutEmpty(key) + return nil + } + switch v := val.(type) { + case int64: + tCtx.Map().PutInt(key, v) + case string: + tCtx.Map().PutStr(key, v) + default: + t.Fatal("unexpected type") + } + return nil + }, + Getter: func(ctx context.Context, tCtx pcommon.Value) (any, error) { + return tCtx, nil + }, + } + } - mg := &ottl.StandardPMapGetter[pcommon.Map]{ - Getter: func(ctx context.Context, tCtx pcommon.Map) (any, error) { + sourceMap := ottl.StandardPMapGetter[pcommon.Value]{ + Getter: func(ctx context.Context, tCtx pcommon.Value) (any, error) { return tCtx, nil }, } tests := []struct { name string - mg ottl.PMapGetter[pcommon.Map] - field string - targetField string + target ottl.GetSetter[pcommon.Value] + sourceMap ottl.PMapGetter[pcommon.Value] + sourceKey string ignoreMissing ottl.Optional[bool] conflictStrategy ottl.Optional[string] - want func(pcommon.Map) + want map[string]any wantErr error }{ { - name: "rename test", - mg: mg, - field: "test", - targetField: "test3", - want: func(expectedMap pcommon.Map) { - expectedMap.PutStr("test3", "hello world") - expectedMap.PutInt("test2", 3) - expectedMap.PutEmpty("empty") + name: "rename test", + target: target("test3"), + sourceMap: sourceMap, + sourceKey: "test", + want: map[string]any{ + "test2": int64(3), + "test3": "hello world", + "empty": nil, }, }, { - name: "rename empty", - mg: mg, - field: "empty", - targetField: "empty3", - want: func(expectedMap pcommon.Map) { - expectedMap.PutStr("test", "hello world") - expectedMap.PutInt("test2", 3) - expectedMap.PutEmpty("empty3") + name: "rename empty", + target: target("empty3"), + sourceMap: sourceMap, + sourceKey: "empty", + want: map[string]any{ + "test": "hello world", + "test2": int64(3), + "empty3": nil, }, }, { - name: "rename ignore missing default", - mg: mg, - field: "test5", - targetField: "test3", - want: func(expectedMap pcommon.Map) { - input.CopyTo(expectedMap) - }, + name: "rename ignore missing default", + target: target("test3"), + sourceMap: sourceMap, + sourceKey: "test5", + want: inputMap, }, { name: "rename ignore missing true", - mg: mg, - field: "test5", - targetField: "test3", + target: target("test3"), + sourceMap: sourceMap, + sourceKey: "test5", ignoreMissing: ottl.NewTestingOptional[bool](true), - want: func(expectedMap pcommon.Map) { - input.CopyTo(expectedMap) - }, + want: inputMap, }, { name: "rename ignore missing false", - mg: mg, - field: "test5", - targetField: "test3", + target: target("test3"), + sourceMap: sourceMap, + sourceKey: "test5", ignoreMissing: ottl.NewTestingOptional[bool](false), wantErr: ErrRenameKeyIsMissing, }, { - name: "rename with default strategy", - mg: mg, - field: "test", - targetField: "test2", - want: func(expectedMap pcommon.Map) { - expectedMap.PutEmpty("empty") - expectedMap.PutStr("test2", "hello world") + name: "rename with default strategy", + target: target("test2"), + sourceMap: sourceMap, + sourceKey: "test", + want: map[string]any{ + "test2": "hello world", + "empty": nil, }, }, { - name: "rename with replace strategy", - mg: mg, - field: "test", - targetField: "test2", - conflictStrategy: ottl.NewTestingOptional[string]("replace"), - want: func(expectedMap pcommon.Map) { - expectedMap.PutEmpty("empty") - expectedMap.PutStr("test2", "hello world") + name: "rename with upsert strategy", + target: target("test2"), + sourceMap: sourceMap, + sourceKey: "test", + conflictStrategy: ottl.NewTestingOptional[string](renameConflictUpsert), + want: map[string]any{ + "test2": "hello world", + "empty": nil, }, }, { name: "rename with fail strategy", - mg: mg, - field: "test", - targetField: "test2", - conflictStrategy: ottl.NewTestingOptional[string]("fail"), + target: target("test2"), + sourceMap: sourceMap, + sourceKey: "test", + conflictStrategy: ottl.NewTestingOptional[string](renameConflictFail), wantErr: ErrRenameKeyAlreadyExists, }, { - name: "rename with ignore strategy", - mg: mg, - field: "test", - targetField: "test2", - conflictStrategy: ottl.NewTestingOptional[string]("ignore"), - want: func(expectedMap pcommon.Map) { - input.CopyTo(expectedMap) - }, + name: "rename with insert strategy", + target: target("test2"), + sourceMap: sourceMap, + sourceKey: "test", + conflictStrategy: ottl.NewTestingOptional[string](renameConflictInsert), + want: inputMap, }, { - name: "rename same key with default strategy", - mg: mg, - field: "test", - targetField: "test", - want: func(expectedMap pcommon.Map) { - input.CopyTo(expectedMap) - }, + name: "rename same key with default strategy", + target: target("test"), + sourceMap: sourceMap, + sourceKey: "test", + want: inputMap, }, { - name: "rename same key with replace strategy", - mg: mg, - field: "test", - targetField: "test", - conflictStrategy: ottl.NewTestingOptional[string]("replace"), - want: func(expectedMap pcommon.Map) { - input.CopyTo(expectedMap) - }, + name: "rename same key with upsert strategy", + target: target("test"), + sourceMap: sourceMap, + sourceKey: "test", + conflictStrategy: ottl.NewTestingOptional[string](renameConflictUpsert), + want: inputMap, }, { name: "rename same key with fail strategy", - mg: mg, - field: "test", - targetField: "test", - conflictStrategy: ottl.NewTestingOptional[string]("fail"), + target: target("test"), + sourceMap: sourceMap, + sourceKey: "test", + conflictStrategy: ottl.NewTestingOptional[string](renameConflictFail), wantErr: ErrRenameKeyAlreadyExists, }, { - name: "rename same key with ignore strategy", - mg: mg, - field: "test", - targetField: "test", - conflictStrategy: ottl.NewTestingOptional[string]("ignore"), - want: func(expectedMap pcommon.Map) { - input.CopyTo(expectedMap) - }, + name: "rename same key with insert strategy", + target: target("test"), + sourceMap: sourceMap, + sourceKey: "test", + conflictStrategy: ottl.NewTestingOptional[string](renameConflictInsert), + want: inputMap, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - scenarioMap := pcommon.NewMap() - input.CopyTo(scenarioMap) + scenarioValue := pcommon.NewValueMap() + input.CopyTo(scenarioValue.Map()) - exprFunc, err := rename(tt.mg, tt.field, tt.targetField, tt.ignoreMissing, tt.conflictStrategy) + exprFunc, err := rename(tt.target, tt.sourceMap, tt.sourceKey, tt.ignoreMissing, tt.conflictStrategy) assert.NoError(t, err) - _, err = exprFunc(nil, scenarioMap) + _, err = exprFunc(nil, scenarioValue) assert.ErrorIs(t, err, tt.wantErr) if err != nil { return } - - expected := pcommon.NewMap() - tt.want(expected) - - assert.Equal(t, expected, scenarioMap) + assert.Equal(t, tt.want, scenarioValue.Map().AsRaw()) }) } } func Test_rename_bad_input(t *testing.T) { input := pcommon.NewValueStr("not a map") - target := &ottl.StandardPMapGetter[any]{ - Getter: func(ctx context.Context, tCtx any) (any, error) { + + target := &ottl.StandardGetSetter[pcommon.Value]{ + Setter: func(ctx context.Context, tCtx pcommon.Value, val any) error { + return nil + }, + Getter: func(ctx context.Context, tCtx pcommon.Value) (any, error) { + return tCtx, nil + }, + } + + sourceMap := ottl.StandardPMapGetter[pcommon.Value]{ + Getter: func(ctx context.Context, tCtx pcommon.Value) (any, error) { return tCtx, nil }, } key := "anything" - exprFunc, err := rename[any](target, key, key, ottl.Optional[bool]{}, ottl.Optional[string]{}) + exprFunc, err := rename(target, sourceMap, key, ottl.Optional[bool]{}, ottl.Optional[string]{}) assert.NoError(t, err) _, err = exprFunc(nil, input) @@ -202,30 +226,48 @@ func Test_rename_bad_input(t *testing.T) { } func Test_rename_bad_conflict_strategy(t *testing.T) { - target := &ottl.StandardPMapGetter[any]{ - Getter: func(ctx context.Context, tCtx any) (any, error) { + target := &ottl.StandardGetSetter[pcommon.Value]{ + Setter: func(ctx context.Context, tCtx pcommon.Value, val any) error { + return nil + }, + Getter: func(ctx context.Context, tCtx pcommon.Value) (any, error) { + return tCtx, nil + }, + } + + sourceMap := ottl.StandardPMapGetter[pcommon.Value]{ + Getter: func(ctx context.Context, tCtx pcommon.Value) (any, error) { return tCtx, nil }, } key := "anything" - _, err := rename[any](target, key, key, ottl.Optional[bool]{}, ottl.NewTestingOptional[string]("unsupported")) + _, err := rename(target, sourceMap, key, ottl.Optional[bool]{}, ottl.NewTestingOptional[string]("unsupported")) assert.ErrorIs(t, err, ErrRenameInvalidConflictStrategy) } func Test_rename_get_nil(t *testing.T) { - target := &ottl.StandardPMapGetter[any]{ - Getter: func(ctx context.Context, tCtx any) (any, error) { + target := &ottl.StandardGetSetter[pcommon.Value]{ + Setter: func(ctx context.Context, tCtx pcommon.Value, val any) error { + return nil + }, + Getter: func(ctx context.Context, tCtx pcommon.Value) (any, error) { + return tCtx, nil + }, + } + + sourceMap := ottl.StandardPMapGetter[pcommon.Value]{ + Getter: func(ctx context.Context, tCtx pcommon.Value) (any, error) { return tCtx, nil }, } key := "anything" - exprFunc, err := rename[any](target, key, key, ottl.Optional[bool]{}, ottl.Optional[string]{}) + exprFunc, err := rename(target, sourceMap, key, ottl.Optional[bool]{}, ottl.Optional[string]{}) assert.NoError(t, err) - _, err = exprFunc(nil, nil) + _, err = exprFunc(nil, pcommon.NewValueEmpty()) assert.Error(t, err) }