Skip to content

Commit 302eae3

Browse files
authored
[pkg/ottl] add ability to compare maps (#38611)
#### Description I realized today we don't have the ability to compare maps. So a condition like `attributes == attributes` returns false. This PR adds the ability to compare maps/pcommon.Maps. #### Testing manual testing and unit tests #### Documentation updated docs
1 parent 8479183 commit 302eae3

File tree

4 files changed

+111
-11
lines changed

4 files changed

+111
-11
lines changed

.chloggen/ottl.map-comparison.yaml

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. filelogreceiver)
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: Add ability to compare maps in Boolean Expressions
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: [38611]
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: []

pkg/ottl/LANGUAGE.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -274,17 +274,18 @@ A `not equal` notation in the table below means that the "!=" operator returns t
274274

275275
The `time.Time` and `time.Duration` types are compared using comparison functions from their respective packages. For more details on how those comparisons work, see the [Golang Time package](https://pkg.go.dev/time).
276276

277-
278-
| base type | bool | int64 | float64 | string | Bytes | nil | time.Time | time.Duration |
279-
|---------------|-------------|---------------------|---------------------|---------------------------------|--------------------------|------------------------|--------------------------------------------------------------|------------------------------------------------------|
280-
| bool | normal, T>F | not equal | not equal | not equal | not equal | not equal | not equal | not equal |
281-
| int64 | not equal | compared as largest | compared as float64 | not equal | not equal | not equal | not equal | not equal |
282-
| float64 | not equal | compared as float64 | compared as largest | not equal | not equal | not equal | not equal | not equal |
283-
| string | not equal | not equal | not equal | normal (compared as Go strings) | not equal | not equal | not equal | not equal |
284-
| Bytes | not equal | not equal | not equal | not equal | byte-for-byte comparison | []byte(nil) == nil | not equal | not equal |
285-
| nil | not equal | not equal | not equal | not equal | []byte(nil) == nil | true for equality only | not equal | not equal |
286-
| time.Time | not equal | not equal | not equal | not equal | not equal | not equal | uses `time.Equal()`to check equality | not equal |
287-
| time.Duration | not equal | not equal | not equal | not equal | not equal | not equal | not equal | uses `time.Before()` and `time.After` for comparison |
277+
| base type | bool | int64 | float64 | string | Bytes | nil | time.Time | time.Duration | map[string]any | pcommon.Map |
278+
|----------------|-------------|---------------------|---------------------|---------------------------------|--------------------------|------------------------|--------------------------------------|------------------------------------------------------|--------------------------------------------------------------|--------------------------------------------------------------|
279+
| bool | normal, T>F | not equal | not equal | not equal | not equal | not equal | not equal | not equal | not equal | not equal |
280+
| int64 | not equal | compared as largest | compared as float64 | not equal | not equal | not equal | not equal | not equal | not equal | not equal |
281+
| float64 | not equal | compared as float64 | compared as largest | not equal | not equal | not equal | not equal | not equal | not equal | not equal |
282+
| string | not equal | not equal | not equal | normal (compared as Go strings) | not equal | not equal | not equal | not equal | not equal | not equal |
283+
| Bytes | not equal | not equal | not equal | not equal | byte-for-byte comparison | []byte(nil) == nil | not equal | not equal | not equal | not equal |
284+
| nil | not equal | not equal | not equal | not equal | []byte(nil) == nil | true for equality only | not equal | not equal | not equal | not equal |
285+
| time.Time | not equal | not equal | not equal | not equal | not equal | not equal | uses `time.Equal()`to check equality | not equal | not equal | not equal |
286+
| time.Duration | not equal | not equal | not equal | not equal | not equal | not equal | not equal | uses `time.Before()` and `time.After` for comparison | not equal | not equal |
287+
| map[string]any | not equal | not equal | not equal | not equal | not equal | not equal | not equal | not equal | uses reflect.DeepEqual for comparison | convert to raw map and uses reflect.DeepEqual for comparison |
288+
| pcommon.Map | not equal | not equal | not equal | not equal | not equal | not equal | not equal | not equal | convert to raw map and uses reflect.DeepEqual for comparison | uses pcommon.Map Equal for comparison |
288289

289290
Examples:
290291
- `name == "a name"`

pkg/ottl/compare.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ package ottl // import "github.com/open-telemetry/opentelemetry-collector-contri
55

66
import (
77
"bytes"
8+
"reflect"
89
"time"
910

11+
"go.opentelemetry.io/collector/pdata/pcommon"
1012
"golang.org/x/exp/constraints"
1113
)
1214

@@ -168,6 +170,49 @@ func (p *Parser[K]) compareTime(a time.Time, b any, op compareOp) bool {
168170
}
169171
}
170172

173+
func (p *Parser[K]) compareMap(a map[string]any, b any, op compareOp) bool {
174+
switch v := b.(type) {
175+
case pcommon.Map:
176+
switch op {
177+
case eq:
178+
return reflect.DeepEqual(a, v.AsRaw())
179+
case ne:
180+
return !reflect.DeepEqual(a, v.AsRaw())
181+
default:
182+
return p.invalidComparison(op)
183+
}
184+
case map[string]any:
185+
switch op {
186+
case eq:
187+
return reflect.DeepEqual(a, v)
188+
case ne:
189+
return !reflect.DeepEqual(a, v)
190+
default:
191+
return p.invalidComparison(op)
192+
}
193+
default:
194+
return p.invalidComparison(op)
195+
}
196+
}
197+
198+
func (p *Parser[K]) comparePMap(a pcommon.Map, b any, op compareOp) bool {
199+
switch v := b.(type) {
200+
case pcommon.Map:
201+
switch op {
202+
case eq:
203+
return a.Equal(v)
204+
case ne:
205+
return !a.Equal(v)
206+
default:
207+
return p.invalidComparison(op)
208+
}
209+
case map[string]any:
210+
return p.compareMap(a.AsRaw(), v, op)
211+
default:
212+
return p.invalidComparison(op)
213+
}
214+
}
215+
171216
// a and b are the return values from a Getter; we try to compare them
172217
// according to the given operator.
173218
func (p *Parser[K]) compare(a any, b any, op compareOp) bool {
@@ -199,6 +244,10 @@ func (p *Parser[K]) compare(a any, b any, op compareOp) bool {
199244
return p.compareDuration(v, b, op)
200245
case time.Time:
201246
return p.compareTime(v, b, op)
247+
case map[string]any:
248+
return p.compareMap(v, b, op)
249+
case pcommon.Map:
250+
return p.comparePMap(v, b, op)
202251
default:
203252
// If we don't know what type it is, we can't do inequalities yet. So we can fall back to the old behavior where we just
204253
// use Go's standard equality.

pkg/ottl/compare_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"testing"
99

1010
"go.opentelemetry.io/collector/component/componenttest"
11+
"go.opentelemetry.io/collector/pdata/pcommon"
1112
)
1213

1314
// Our types are bool, int, float, string, Bytes, nil, so we compare all types in both directions.
@@ -24,6 +25,13 @@ var (
2425
i64b = int64(2)
2526
f64a = float64(1)
2627
f64b = float64(2)
28+
29+
m1 = map[string]any{
30+
"test": true,
31+
}
32+
m2 = map[string]any{
33+
"test": false,
34+
}
2735
)
2836

2937
type testA struct {
@@ -41,6 +49,12 @@ type testB struct {
4149
// every other basic type, and includes a pretty good set of tests on the pointers to all the
4250
// basic types as well.
4351
func Test_compare(t *testing.T) {
52+
pm1 := pcommon.NewMap()
53+
pm1.PutBool("test", true)
54+
55+
pm2 := pcommon.NewMap()
56+
pm2.PutBool("test", false)
57+
4458
tests := []struct {
4559
name string
4660
a any
@@ -100,6 +114,15 @@ func Test_compare(t *testing.T) {
100114
{"non-prim, diff type", testA{"hi"}, testB{"hi"}, []bool{false, true, false, false, false, false}},
101115
{"non-prim, int type", testA{"hi"}, 5, []bool{false, true, false, false, false, false}},
102116
{"int, non-prim", 5, testA{"hi"}, []bool{false, true, false, false, false, false}},
117+
118+
{"maps diff", m1, m2, []bool{false, true, false, false, false, false}},
119+
{"maps same", m1, m1, []bool{true, false, false, false, false, false}},
120+
{"pmaps diff", pm1, pm2, []bool{false, true, false, false, false, false}},
121+
{"pmaps same", pm1, pm1, []bool{true, false, false, false, false, false}},
122+
{"mixed maps diff", m1, pm2, []bool{false, true, false, false, false, false}},
123+
{"mixed maps same", m1, pm1, []bool{true, false, false, false, false, false}},
124+
{"map and other type", m1, sa, []bool{false, true, false, false, false, false}},
125+
{"pmap and other type", pm1, sa, []bool{false, true, false, false, false, false}},
103126
}
104127
ops := []compareOp{eq, ne, lt, lte, gte, gt}
105128
for _, tt := range tests {

0 commit comments

Comments
 (0)