Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pkg/telemetryquerylanguage] Support Inequalities in TQL #13320

Merged
merged 54 commits into from
Sep 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
c0251e5
Add inequalities and a lot of tests
kentquirk Aug 12, 2022
b68599e
More comments, more tests
kentquirk Aug 12, 2022
0962343
More test work
kentquirk Aug 13, 2022
8a98c4e
More evaluator tests
kentquirk Aug 13, 2022
e57d5b1
Add docs to readme
kentquirk Aug 13, 2022
66868cd
Add changelog file
kentquirk Aug 13, 2022
ad0705c
Change nil comparison logic to spec
kentquirk Aug 13, 2022
7a9f328
Clean up tests
kentquirk Aug 13, 2022
bd39795
Fix comment
kentquirk Aug 13, 2022
82f3109
add license
kentquirk Aug 13, 2022
4e8c6cd
Make code less clear to pacify a linter
kentquirk Aug 13, 2022
b0f2040
Fine.
kentquirk Aug 13, 2022
fb06a67
goporto
kentquirk Aug 13, 2022
05b3860
Grr.
kentquirk Aug 13, 2022
83ff7d7
Merge branch 'main' into tql-inequalities
kentquirk Aug 17, 2022
7b8eee7
Fall back to go standard for non-primitive types
kentquirk Aug 17, 2022
f674ddd
Merge branch 'main' into tql-inequalities
kentquirk Aug 17, 2022
1b7b2c7
Add some tests for non-primitive types
kentquirk Aug 17, 2022
6996fe5
Add inequalities and a lot of tests
kentquirk Aug 12, 2022
fbc15df
More comments, more tests
kentquirk Aug 12, 2022
349a6ea
More test work
kentquirk Aug 13, 2022
79654fc
More evaluator tests
kentquirk Aug 13, 2022
ff03cef
Add docs to readme
kentquirk Aug 13, 2022
18839fb
Add changelog file
kentquirk Aug 13, 2022
8af12e1
Change nil comparison logic to spec
kentquirk Aug 13, 2022
f2162a8
Clean up tests
kentquirk Aug 13, 2022
f36ad81
Fix comment
kentquirk Aug 13, 2022
d29aab1
add license
kentquirk Aug 13, 2022
f5341a7
Make code less clear to pacify a linter
kentquirk Aug 13, 2022
8c6a79c
Fine.
kentquirk Aug 13, 2022
bea4724
goporto
kentquirk Aug 13, 2022
ba9d5e5
Grr.
kentquirk Aug 13, 2022
ce3ac4d
Fall back to go standard for non-primitive types
kentquirk Aug 17, 2022
bd26fca
Add some tests for non-primitive types
kentquirk Aug 17, 2022
fe9b41c
merge rebase
kentquirk Aug 18, 2022
c37f36d
Generify comparisons a bit
kentquirk Aug 18, 2022
8431023
make gotidy
kentquirk Aug 18, 2022
0851a38
respond to PR feedback
kentquirk Aug 22, 2022
79a1aaa
Remove now-invalid test
kentquirk Aug 22, 2022
4c637b4
Merge branch 'main' into tql-inequalities
kentquirk Aug 22, 2022
a7310c6
Parser tags now use structtag-compatible syntax
kentquirk Aug 22, 2022
adcd4c5
Make parser generate CompareOp
kentquirk Aug 22, 2022
aa87f48
Clean up CompareOp a bit more
kentquirk Aug 22, 2022
6eb2ff8
Support only the *64 types; drop pointers too.
kentquirk Aug 22, 2022
481e768
Merge branch 'main' into tql-inequalities
kentquirk Aug 22, 2022
8008099
Consistency
kentquirk Aug 22, 2022
5f1d880
Merge branch 'tql-inequalities' of github.com:honeycombio/opentelemet…
kentquirk Aug 22, 2022
d90f40e
Merge branch 'main' into tql-inequalities
kentquirk Aug 22, 2022
efba874
Merge branch 'main' into tql-inequalities
kentquirk Aug 22, 2022
ce9fc19
Merge branch 'main' into tql-inequalities
kentquirk Aug 22, 2022
a52fbc2
Small change to force checks to run
kentquirk Aug 23, 2022
2dfa8c5
Merge branch 'main' into tql-inequalities
kentquirk Aug 26, 2022
3a622e2
Correct errors in README
kentquirk Aug 29, 2022
0164c35
Merge branch 'main' into tql-inequalities
kentquirk Aug 29, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/configschema/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ require (
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.23.0 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75 // indirect
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
golang.org/x/net v0.0.0-20220809184613-07c6da5e1ced // indirect
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
Expand Down
4 changes: 2 additions & 2 deletions cmd/configschema/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ require (
go.uber.org/goleak v1.1.12 // indirect
go.uber.org/zap v1.23.0 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75 // indirect
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220809184613-07c6da5e1ced // indirect
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pkg/telemetryquerylanguage/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
go.opentelemetry.io/collector/pdata v0.58.1-0.20220825025657-e092fc728b72
go.opentelemetry.io/otel/trace v1.9.0
go.uber.org/multierr v1.8.0
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
)

require (
Expand All @@ -23,7 +24,7 @@ require (
go.opentelemetry.io/otel v1.9.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
golang.org/x/text v0.3.3 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
google.golang.org/grpc v1.49.0 // indirect
Expand Down
6 changes: 4 additions & 2 deletions pkg/telemetryquerylanguage/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 33 additions & 5 deletions pkg/telemetryquerylanguage/tql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The TQL grammar includes Invocations, Values and Expressions.

### Invocations

Invocations represent a function call. Invocations are made up of 2 parts
Invocations represent a function call. Invocations are made up of 2 parts:

- a string identifier. The string identifier must start with a letter or an underscore (`_`).
- zero or more Values (comma separated) surrounded by parentheses (`()`).
Expand Down Expand Up @@ -110,18 +110,46 @@ Booleans can be either:
- A literal boolean value (`true` or `false`).
- A Comparison, made up of a left Value, an operator, and a right Value. See [Values](#values) for details on what a Value can be.

Operators determine how the two Values are compared. The valid operators are:
Operators determine how the two Values are compared.

- Equal (`==`). Equal (`==`) checks if the left and right Values are equal, using Go's `==` operator.
- Not Equal (`!=`). Not Equal (`!=`) checks if the left and right Values are not equal, using Go's `!=` operator.
The valid operators are:

- Equal (`==`). Tests if the left and right Values are equal (see the Comparison Rules below).
- Not Equal (`!=`). Tests if the left and right Values are not equal.
- Less Than (`<`). Tests if left is less than right.
- Greater Than (`>`). Tests if left is greater than right.
TylerHelmuth marked this conversation as resolved.
Show resolved Hide resolved
TylerHelmuth marked this conversation as resolved.
Show resolved Hide resolved
- Less Than or Equal To (`<=`). Tests if left is less than or equal to right.
- Greater Than or Equal to (`>=`). Tests if left is greater than or equal to right.

### Comparison Rules

The table below describes what happens when two Values are compared. Value types are provided by the user of TQL. All of the value types supported by TQL are listed in this table.

If numeric values are of different types, they are compared as `float64`.

For numeric values and strings, the comparison rules are those implemented by Go. Numeric values are done with signed comparisons. For binary values, `false` is considered to be less than `true`.

For values that are not one of the basic primitive types, the only valid comparisons are Equal and Not Equal, which are implemented using Go's standard `==` and `!=` operators.

A `not equal` notation in the table below means that the "!=" operator returns true, but any other operator returns false. Note that a nil byte array is considered equivalent to nil.


| base type | bool | int64 | float64 | string | Bytes | nil |
| --------- | ----------- | ------------------- | ------------------- | ------------------------------- | ------------------------ | ---------------------- |
| bool | normal, T>F | not equal | not equal | not equal | not equal | not equal |
| int64 | not equal | compared as largest | compared as float64 | not equal | not equal | not equal |
| float64 | not equal | compared as float64 | compared as largest | not equal | not equal | not equal |
| string | not equal | not equal | not equal | normal (compared as Go strings) | not equal | not equal |
| Bytes | not equal | not equal | not equal | not equal | byte-for-byte comparison | []byte(nil) == nil |
| nil | not equal | not equal | not equal | not equal | []byte(nil) == nil | true for equality only |

## Accessing signal telemetry

Access to signal telemetry is provided to TQL functions through a `TransformContext` that is created by the user and passed during statement evaluation. To allow functions to operate on the `TransformContext`, the TQL provides `Getter`, `Setter`, and `GetSetter` interfaces.

### Getters and Setters

Getters allow for reading the following types of data. See the respective section of each Value type for how they are interpreted.
Getters allow for reading the following types of data. See the respective section of each Value type for how they are interpreted.
- [Paths](#paths).
- [Enums](#enums).
- [Literals](#literals).
Expand Down
22 changes: 6 additions & 16 deletions pkg/telemetryquerylanguage/tql/boolean_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,27 +64,17 @@ func newComparisonEvaluator(comparison *Comparison, functions map[string]interfa
return nil, err
}
right, err := NewGetter(comparison.Right, functions, pathParser, enumParser)
// TODO(anuraaga): Check if both left and right are literals and const-evaluate
if err != nil {
return nil, err
}

switch comparison.Op {
case "==":
return func(ctx TransformContext) bool {
a := left.Get(ctx)
b := right.Get(ctx)
return a == b
}, nil
case "!=":
return func(ctx TransformContext) bool {
a := left.Get(ctx)
b := right.Get(ctx)
return a != b
}, nil
}
// The parser ensures that we'll never get an invalid comparison.Op, so we don't have to check that case.
return func(ctx TransformContext) bool {
a := left.Get(ctx)
b := right.Get(ctx)
return compare(a, b, comparison.Op)
}, nil

return nil, fmt.Errorf("unrecognized boolean operation %v", comparison.Op)
}

func newBooleanExpressionEvaluator(expr *BooleanExpression, functions map[string]interface{}, pathParser PathExpressionParser, enumParser EnumParser) (BoolExpressionEvaluator, error) {
Expand Down
194 changes: 85 additions & 109 deletions pkg/telemetryquerylanguage/tql/boolean_value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,116 +15,104 @@
package tql

import (
"strings"
"testing"

"github.com/stretchr/testify/assert"

"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/telemetryquerylanguage/tql/tqltest"
)

func Test_newComparisonEvaluator(t *testing.T) {
kentquirk marked this conversation as resolved.
Show resolved Hide resolved
tests := []struct {
name string
comparison *Comparison
item interface{}
}{
{
name: "literals match",
comparison: &Comparison{
Left: Value{
String: tqltest.Strp("hello"),
},
Right: Value{
String: tqltest.Strp("hello"),
},
Op: "==",
},
},
{
name: "literals don't match",
comparison: &Comparison{
Left: Value{
String: tqltest.Strp("hello"),
},
Right: Value{
String: tqltest.Strp("goodbye"),
},
Op: "!=",
},
},
{
name: "path expression matches",
comparison: &Comparison{
Left: Value{
Path: &Path{
Fields: []Field{
{
Name: "name",
},
},
},
},
Right: Value{
String: tqltest.Strp("bear"),
},
Op: "==",
},
item: "bear",
},
{
name: "path expression not matches",
comparison: &Comparison{
Left: Value{
Path: &Path{
Fields: []Field{
{
Name: "name",
},
},
// valueFor is a test helper to eliminate a lot of tedium in writing tests of Comparisons.
func valueFor(x any) Value {
val := Value{}
switch v := x.(type) {
case []byte:
var b Bytes = v
val.Bytes = &b
case string:
switch {
case v == "NAME":
// if the string is NAME construct a path of "name".
val.Path = &Path{
Fields: []Field{
{
Name: "name",
},
},
Right: Value{
String: tqltest.Strp("cat"),
},
Op: "!=",
},
item: "bear",
},
{
name: "no condition",
comparison: nil,
},
}
case strings.Contains(v, "ENUM"):
// if the string contains ENUM construct an EnumSymbol from it.
val.Enum = (*EnumSymbol)(tqltest.Strp(v))
default:
val.String = tqltest.Strp(v)
}
case float64:
val.Float = tqltest.Floatp(v)
case *float64:
val.Float = v
case int:
val.Int = tqltest.Intp(int64(v))
case *int64:
val.Int = v
case bool:
val.Bool = Booleanp(Boolean(v))
case nil:
var n IsNil = true
val.IsNil = &n
default:
panic("test error!")
}
return val
}

{
name: "compare Enum to int",
comparison: &Comparison{
Left: Value{
Enum: (*EnumSymbol)(tqltest.Strp("TEST_ENUM")),
},
Right: Value{
Int: tqltest.Intp(0),
},
Op: "==",
},
},
{
name: "compare int to Enum",
comparison: &Comparison{
Left: Value{
Int: tqltest.Intp(2),
},
Op: "==",
Right: Value{
Enum: (*EnumSymbol)(tqltest.Strp("TEST_ENUM_TWO")),
},
},
},
// comparison is a test helper that constructs a Comparison object using valueFor
func comparison(left any, right any, op string) *Comparison {
return &Comparison{
Left: valueFor(left),
Right: valueFor(right),
Op: compareOpTable[op],
}
}

func Test_newComparisonEvaluator(t *testing.T) {
tests := []struct {
name string
l any
r any
op string
item interface{}
want bool
}{
{"literals match", "hello", "hello", "==", nil, true},
{"literals don't match", "hello", "goodbye", "!=", nil, true},
{"path expression matches", "NAME", "bear", "==", "bear", true},
{"path expression not matches", "NAME", "cat", "!=", "bear", true},
{"compare Enum to int", "TEST_ENUM", int(0), "==", nil, true},
{"compare int to Enum", int(2), "TEST_ENUM_TWO", "==", nil, true},
{"2 > Enum 0", int(2), "TEST_ENUM", ">", nil, true},
{"not 2 < Enum 0", int(2), "TEST_ENUM", "<", nil, false},
{"not 6 == 3.14", 6, 3.14, "==", nil, false},
{"6 != 3.14", 6, 3.14, "!=", nil, true},
{"6 > 3.14", 6, 3.14, ">", nil, true},
{"6 >= 3.14", 6, 3.14, ">=", nil, true},
{"not 6 < 3.14", 6, 3.14, "<", nil, false},
{"not 6 <= 3.14", 6, 3.14, "<=", nil, false},
{"'foo' > 'bar'", "foo", "bar", ">", nil, true},
{"'foo' > bear", "foo", "NAME", ">", "bear", true},
{"true > false", true, false, ">", nil, true},
{"not true > 0", true, 0, ">", nil, false},
{"not 'true' == true", "true", true, "==", nil, false},
{"[]byte('a') < []byte('b')", []byte("a"), []byte("b"), "<", nil, true},
{"nil == nil", nil, nil, "==", nil, true},
{"nil == []byte(nil)", nil, []byte(nil), "==", nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
evaluate, err := newComparisonEvaluator(tt.comparison, DefaultFunctionsForTests(), testParsePath, testParseEnum)
comp := comparison(tt.l, tt.r, tt.op)
evaluate, err := newComparisonEvaluator(comp, DefaultFunctionsForTests(), testParsePath, testParseEnum)
assert.NoError(t, err)
assert.True(t, evaluate(tqltest.TestTransformContext{
assert.Equal(t, tt.want, evaluate(tqltest.TestTransformContext{
Item: tt.item,
}))
})
Expand All @@ -136,25 +124,13 @@ func Test_newConditionEvaluator_invalid(t *testing.T) {
name string
comparison *Comparison
}{
{
name: "unknown operation",
comparison: &Comparison{
Left: Value{
String: tqltest.Strp("bear"),
},
Op: "<>",
Right: Value{
String: tqltest.Strp("cat"),
},
},
},
{
name: "unknown Path",
comparison: &Comparison{
Left: Value{
Enum: (*EnumSymbol)(tqltest.Strp("SYMBOL_NOT_FOUND")),
},
Op: "==",
Op: EQ,
Right: Value{
String: tqltest.Strp("trash"),
},
Expand Down
Loading