Files
o11y/pkg/query-service/app/resource/resource_query_builder_test.go
zeekay c3dc2f1ecf Read the event plane, not what it replaced
HIP-0132 unified o11y_logs, o11y_traces and o11y_metrics into one `event`
database and dropped them. The commit that restored the reads repointed
three files and said so; it was thirty. The rest went on naming databases
that no longer exist, so trace funnels, the Kafka and queue panels, the
v3/v4 logs and traces builders and the PromQL adapter each answered
Code: 81 UNKNOWN_DATABASE, and the golden tests agreed with them because
they pinned the same dead SQL.

pkg/telemetryplane is the one address for the name, imports nothing, and
is reachable from every depth that needs it. Its test walks the tree and
fails the build on a literal that names a dropped database, tests
included — a golden encoding broken SQL is a second vote for the bug, not
coverage.

Two service views turned a failed read into an offer to instrument an
already-instrumented application. A failure now shows the failure, and
onboarding is offered on a successful empty read.

Cherry-picked from salvage/blue-wt-o11y-readers 83ecbec03c.
2026-09-08 23:07:06 -07:00

557 lines
14 KiB
Go

package resource
import (
"reflect"
"testing"
v3 "github.com/hanzoai/o11y/pkg/query-service/model/v3"
"github.com/hanzoai/o11y/pkg/telemetrylogs"
"github.com/hanzoai/o11y/pkg/telemetryplane"
)
func Test_buildResourceFilter(t *testing.T) {
type args struct {
logsOp string
key string
op v3.FilterOperator
value interface{}
}
tests := []struct {
name string
args args
want string
}{
{
name: "test exists",
args: args{
key: "service.name",
op: v3.FilterOperatorExists,
},
want: `simpleJSONHas(labels, 'service.name')`,
},
{
name: "test nexists",
args: args{
key: "service.name",
op: v3.FilterOperatorNotExists,
},
want: `not simpleJSONHas(labels, 'service.name')`,
},
{
name: "test regex",
args: args{
logsOp: "match(%s, %s)",
key: "service.name",
op: v3.FilterOperatorRegex,
value: ".*",
},
want: `match(simpleJSONExtractString(labels, 'service.name'), '.*')`,
},
{
name: "test contains",
args: args{
logsOp: "LIKE",
key: "service.name",
op: v3.FilterOperatorContains,
value: "Application%_",
},
want: `simpleJSONExtractString(lower(labels), 'service.name') LIKE '%application\%\_%'`,
},
{
name: "test eq",
args: args{
logsOp: "=",
key: "service.name",
op: v3.FilterOperatorEqual,
value: "Application%",
},
want: `simpleJSONExtractString(labels, 'service.name') = 'Application%'`,
},
{
name: "test value with quotes",
args: args{
logsOp: "=",
key: "service.name",
op: v3.FilterOperatorEqual,
value: "Application's",
},
want: `simpleJSONExtractString(labels, 'service.name') = 'Application\'s'`,
},
{
name: "test like",
args: args{
logsOp: "LIKE",
key: "service.name",
op: v3.FilterOperatorLike,
value: "Application%_",
},
want: `simpleJSONExtractString(lower(labels), 'service.name') LIKE 'application%_'`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := buildResourceFilter(tt.args.logsOp, tt.args.key, tt.args.op, tt.args.value); got != tt.want {
t.Errorf("buildResourceFilter() = %v, want %v", got, tt.want)
}
})
}
}
func Test_buildIndexFilterForInOperator(t *testing.T) {
type args struct {
key string
op v3.FilterOperator
value interface{}
}
tests := []struct {
name string
args args
want string
}{
{
name: "test in array",
args: args{
key: "service.name",
op: v3.FilterOperatorIn,
value: []interface{}{"Application", "Test"},
},
want: `(labels like '%"service.name":"Application"%' OR labels like '%"service.name":"Test"%')`,
},
{
name: "test nin array",
args: args{
key: "service.name",
op: v3.FilterOperatorNotIn,
value: []interface{}{"Application", "Test"},
},
want: `(labels not like '%"service.name":"Application"%' AND labels not like '%"service.name":"Test"%')`,
},
{
name: "test in string",
args: args{
key: "service.name",
op: v3.FilterOperatorIn,
value: "application%",
},
want: `(labels like '%"service.name":"application\%"%')`,
},
{
name: "test nin string",
args: args{
key: "service.name",
op: v3.FilterOperatorNotIn,
value: `application'"_s`,
},
want: `(labels not like '%"service.name":"application\'\\\\"\_s"%')`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := buildIndexFilterForInOperator(tt.args.key, tt.args.op, tt.args.value); got != tt.want {
t.Errorf("buildIndexFilterForInOperator() = %v, want %v", got, tt.want)
}
})
}
}
func Test_buildResourceIndexFilter(t *testing.T) {
type args struct {
key string
op v3.FilterOperator
value interface{}
}
tests := []struct {
name string
args args
want string
}{
{
name: "test eq",
args: args{
key: "service.name",
op: v3.FilterOperatorEqual,
value: `Application"`,
},
want: `labels like '%service.name":"Application\\\\"%'`,
},
{
name: "test not eq",
args: args{
key: "service.name",
op: v3.FilterOperatorNotEqual,
value: `Application"`,
},
want: `labels not like '%service.name":"Application\\\\"%'`,
},
{
name: "test like with % and _",
args: args{
key: "service.name",
op: v3.FilterOperatorLike,
value: "Application%_test",
},
want: `lower(labels) like '%service.name%application%_test%'`,
},
{
name: "test like with % and _",
args: args{
key: "service.name",
op: v3.FilterOperatorLike,
value: "application%_test",
},
want: `lower(labels) like '%service.name%application%_test%'`,
},
{
name: "test not like with % and _",
args: args{
key: "service.name",
op: v3.FilterOperatorLike,
value: "application%_test",
},
want: `lower(labels) like '%service.name%application%_test%'`,
},
{
name: "test not like with % and _",
args: args{
key: "service.name",
op: v3.FilterOperatorILike,
value: "application%_test",
},
want: `lower(labels) like '%service.name%application%_test%'`,
},
{
name: "test contains",
args: args{
key: "service.name",
op: v3.FilterOperatorContains,
value: "application",
},
want: `lower(labels) like '%service.name%application%'`,
},
{
name: "test not contains",
args: args{
key: "service.name",
op: v3.FilterOperatorNotContains,
value: "application",
},
want: ``,
},
{
name: "test contains with % and _",
args: args{
key: "service.name",
op: v3.FilterOperatorNotContains,
value: "application%_test",
},
want: ``,
},
{
name: "test not regex",
args: args{
key: "service.name",
op: v3.FilterOperatorNotRegex,
value: ".*",
},
want: ``,
},
{
name: "test in",
args: args{
key: "service.name",
op: v3.FilterOperatorNotIn,
value: []interface{}{"Application", "Test"},
},
want: `(labels not like '%"service.name":"Application"%' AND labels not like '%"service.name":"Test"%')`,
},
{
name: "test exists",
args: args{
key: "service.name",
op: v3.FilterOperatorExists,
},
want: `lower(labels) like '%service.name%'`,
},
{
name: "test not exists",
args: args{
key: "service.name",
op: v3.FilterOperatorNotExists,
},
want: `lower(labels) not like '%service.name%'`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := buildResourceIndexFilter(tt.args.key, tt.args.op, tt.args.value); got != tt.want {
t.Errorf("buildResourceIndexFilter() = %v, want %v", got, tt.want)
}
})
}
}
func Test_buildResourceFiltersFromFilterItems(t *testing.T) {
type args struct {
fs *v3.FilterSet
}
tests := []struct {
name string
args args
want []string
wantErr bool
}{
{
name: "ignore attribute",
args: args{
fs: &v3.FilterSet{
Items: []v3.FilterItem{
{
Key: v3.AttributeKey{
Key: "service.name",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
Operator: v3.FilterOperatorEqual,
Value: "test",
},
},
},
},
want: nil,
wantErr: false,
},
{
name: "build filter",
args: args{
fs: &v3.FilterSet{
Items: []v3.FilterItem{
{
Key: v3.AttributeKey{
Key: "service.name",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeResource,
},
Operator: v3.FilterOperatorEqual,
Value: "test",
},
},
},
},
want: []string{
"simpleJSONExtractString(labels, 'service.name') = 'test'",
"labels like '%service.name\":\"test%'",
},
wantErr: false,
},
{
name: "build filter with multiple items",
args: args{
fs: &v3.FilterSet{
Items: []v3.FilterItem{
{
Key: v3.AttributeKey{
Key: "service.name",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeResource,
},
Operator: v3.FilterOperatorEqual,
Value: "test",
},
{
Key: v3.AttributeKey{
Key: "namespace",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeResource,
},
Operator: v3.FilterOperatorContains,
Value: `test1"`,
},
},
},
},
want: []string{
"simpleJSONExtractString(labels, 'service.name') = 'test'",
"labels like '%service.name\":\"test%'",
`simpleJSONExtractString(lower(labels), 'namespace') LIKE '%test1"%'`,
`lower(labels) like '%namespace%test1\\\\"%'`,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := buildResourceFiltersFromFilterItems(tt.args.fs)
if (err != nil) != tt.wantErr {
t.Errorf("buildResourceFiltersFromFilterItems() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("buildResourceFiltersFromFilterItems() = %v, want %v", got, tt.want)
}
})
}
}
func Test_buildResourceFiltersFromGroupBy(t *testing.T) {
type args struct {
groupBy []v3.AttributeKey
}
tests := []struct {
name string
args args
want []string
}{
{
name: "build filter",
args: args{
groupBy: []v3.AttributeKey{
{
Key: "service.name",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeResource,
},
},
},
want: []string{
"(simpleJSONHas(labels, 'service.name') AND labels like '%service.name%')",
},
},
{
name: "build filter multiple group by",
args: args{
groupBy: []v3.AttributeKey{
{
Key: "service.name",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeResource,
},
{
Key: "namespace",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeResource,
},
},
},
want: []string{
"(simpleJSONHas(labels, 'service.name') AND labels like '%service.name%')",
"(simpleJSONHas(labels, 'namespace') AND labels like '%namespace%')",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := buildResourceFiltersFromGroupBy(tt.args.groupBy); !reflect.DeepEqual(got, tt.want) {
t.Errorf("buildResourceFiltersFromGroupBy() = %v, want %v", got, tt.want)
}
})
}
}
func Test_buildResourceFiltersFromAggregateAttribute(t *testing.T) {
type args struct {
aggregateAttribute v3.AttributeKey
}
tests := []struct {
name string
args args
want string
}{
{
name: "build filter",
args: args{
aggregateAttribute: v3.AttributeKey{
Key: "service.name",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeResource,
},
},
want: "(simpleJSONHas(labels, 'service.name') AND labels like '%service.name%')",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := buildResourceFiltersFromAggregateAttribute(tt.args.aggregateAttribute); got != tt.want {
t.Errorf("buildResourceFiltersFromAggregateAttribute() = %v, want %v", got, tt.want)
}
})
}
}
func Test_buildResourceSubQuery(t *testing.T) {
type args struct {
bucketStart int64
bucketEnd int64
fs *v3.FilterSet
groupBy []v3.AttributeKey
aggregateAttribute v3.AttributeKey
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "build sub query",
args: args{
bucketStart: 1680064560,
bucketEnd: 1680066458,
fs: &v3.FilterSet{
Items: []v3.FilterItem{
{
Key: v3.AttributeKey{
Key: "service.name",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeResource,
},
Operator: v3.FilterOperatorEqual,
Value: "test",
},
{
Key: v3.AttributeKey{
Key: "namespace",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeResource,
},
Operator: v3.FilterOperatorContains,
Value: "test1",
},
},
},
groupBy: []v3.AttributeKey{
{
Key: "host.name",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeResource,
},
},
aggregateAttribute: v3.AttributeKey{
Key: "cluster.name",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeResource,
},
},
want: "(SELECT fingerprint FROM event.log_resource WHERE " +
"(seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND " +
"simpleJSONExtractString(labels, 'service.name') = 'test' AND labels like '%service.name\":\"test%' " +
"AND simpleJSONExtractString(lower(labels), 'namespace') LIKE '%test1%' AND lower(labels) like '%namespace%test1%' " +
"AND (simpleJSONHas(labels, 'cluster.name') AND labels like '%cluster.name%') AND " +
"( (simpleJSONHas(labels, 'host.name') AND labels like '%host.name%') ))",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := BuildResourceSubQuery(telemetryplane.DBName, telemetrylogs.LogResourceTableName, tt.args.bucketStart, tt.args.bucketEnd, tt.args.fs, tt.args.groupBy, tt.args.aggregateAttribute, false)
if (err != nil) != tt.wantErr {
t.Errorf("buildResourceSubQuery() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("buildResourceSubQuery() = %v, want %v", got, tt.want)
}
})
}
}