blob: b4fa5885bed823a025f5cf217a2a7355b9953801 [file] [log] [blame]
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package logical_test
import (
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
apiv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/v1"
"github.com/apache/skywalking-banyandb/banyand/series"
"github.com/apache/skywalking-banyandb/pkg/posting/roaring"
"github.com/apache/skywalking-banyandb/pkg/query/executor"
"github.com/apache/skywalking-banyandb/pkg/query/logical"
)
func TestPlanExecution_Limit(t *testing.T) {
assert := require.New(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m, s := prepareSchema(assert)
tests := []struct {
name string
unresolvedPlan logical.UnresolvedPlan
wantLength int
}{
{
name: "Limit 1",
unresolvedPlan: logical.Limit(newMockDataFactory(ctrl, m, s, 20).MockParentPlan(), 1),
wantLength: 1,
},
{
name: "Limit 10",
unresolvedPlan: logical.Limit(newMockDataFactory(ctrl, m, s, 20).MockParentPlan(), 10),
wantLength: 10,
},
{
name: "Limit 50",
unresolvedPlan: logical.Limit(newMockDataFactory(ctrl, m, s, 20).MockParentPlan(), 50),
wantLength: 20,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert := require.New(t)
plan, err := tt.unresolvedPlan.Analyze(s)
assert.NoError(err)
assert.NotNil(plan)
ec := executor.NewMockExecutionContext(ctrl)
entities, err := plan.Execute(ec)
assert.NoError(err)
assert.Len(entities, tt.wantLength)
})
}
}
func TestPlanExecution_Offset(t *testing.T) {
assert := require.New(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m, s := prepareSchema(assert)
tests := []struct {
name string
unresolvedPlan logical.UnresolvedPlan
wantLength int
}{
{
name: "Offset 0",
unresolvedPlan: logical.Offset(newMockDataFactory(ctrl, m, s, 20).MockParentPlan(), 0),
wantLength: 20,
},
{
name: "Offset 10",
unresolvedPlan: logical.Offset(newMockDataFactory(ctrl, m, s, 20).MockParentPlan(), 10),
wantLength: 10,
},
{
name: "Limit 50",
unresolvedPlan: logical.Offset(newMockDataFactory(ctrl, m, s, 20).MockParentPlan(), 50),
wantLength: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert := require.New(t)
plan, err := tt.unresolvedPlan.Analyze(s)
assert.NoError(err)
assert.NotNil(plan)
ec := executor.NewMockExecutionContext(ctrl)
entities, err := plan.Execute(ec)
assert.NoError(err)
assert.Len(entities, tt.wantLength)
})
}
}
func TestPlanExecution_TraceIDFetch(t *testing.T) {
assert := require.New(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m, s := prepareSchema(assert)
traceID := "asdf1234"
p, err := logical.TraceIDFetch(traceID, m).Analyze(s)
assert.NoError(err)
assert.NotNil(p)
f := newMockDataFactory(ctrl, m, s, 10)
entities, err := p.Execute(f.MockTraceIDFetch(traceID))
assert.NoError(err)
assert.Len(entities, 10)
}
func TestPlanExecution_IndexScan(t *testing.T) {
assert := require.New(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m, s := prepareSchema(assert)
st, et := time.Now().Add(-3*time.Hour), time.Now()
tests := []struct {
name string
unresolvedPlan logical.UnresolvedPlan
wantLength int
indexMatchers []*indexMatcher
}{
{
name: "Single Index Search",
unresolvedPlan: logical.IndexScan(st.UnixNano(), et.UnixNano(), m, []logical.Expr{
logical.Eq(logical.NewFieldRef("http.method"), logical.Str("GET")),
}, series.TraceStateDefault),
indexMatchers: []*indexMatcher{newIndexMatcher("http.method", 0, roaring.NewPostingListWithInitialData(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))},
wantLength: 10,
},
{
name: "Multiple Index Search",
unresolvedPlan: logical.IndexScan(st.UnixNano(), et.UnixNano(), m, []logical.Expr{
logical.Eq(logical.NewFieldRef("http.method"), logical.Str("GET")),
logical.Eq(logical.NewFieldRef("service_id"), logical.Str("app")),
}, series.TraceStateDefault),
indexMatchers: []*indexMatcher{
newIndexMatcher("http.method", 0, roaring.NewPostingListWithInitialData(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)),
newIndexMatcher("service_id", 0, roaring.NewPostingListWithInitialData(1, 3, 5, 7, 9)),
},
wantLength: 5,
},
{
name: "Multiple Index With One Empty Result(ChunkID)",
unresolvedPlan: logical.IndexScan(st.UnixNano(), et.UnixNano(), m, []logical.Expr{
logical.Eq(logical.NewFieldRef("http.method"), logical.Str("GET")),
logical.Eq(logical.NewFieldRef("service_id"), logical.Str("app")),
}, series.TraceStateDefault),
indexMatchers: []*indexMatcher{
newIndexMatcher("http.method", 0, roaring.NewPostingListWithInitialData(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)),
newIndexMatcher("service_id", 0, roaring.NewPostingList()),
},
wantLength: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert := require.New(t)
p, err := tt.unresolvedPlan.Analyze(s)
assert.NoError(err)
assert.NotNil(p)
f := newMockDataFactory(ctrl, m, s, 0)
entities, err := p.Execute(f.MockIndexScan(st, et, tt.indexMatchers...))
assert.NoError(err)
assert.NotNil(entities)
assert.Len(entities, tt.wantLength)
})
}
}
func TestPlanExecution_OrderBy(t *testing.T) {
assert := require.New(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m, s := prepareSchema(assert)
tests := []struct {
name string
targetField string
// TODO: avoid hardcoded index?
targetFieldIdx int
sortDirection apiv1.QueryOrder_Sort
}{
{
name: "Sort By trace_id ASC",
targetField: "trace_id",
targetFieldIdx: 0,
sortDirection: apiv1.QueryOrder_SORT_ASC,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert := require.New(t)
p, err := logical.OrderBy(newMockDataFactory(ctrl, m, s, 20).MockParentPlan(), tt.targetField, tt.sortDirection).Analyze(s)
assert.NoError(err)
assert.NotNil(p)
ec := executor.NewMockExecutionContext(ctrl)
entities, err := p.Execute(ec)
assert.NoError(err)
assert.NotNil(entities)
assert.True(logical.Sorted(entities, tt.targetFieldIdx, tt.sortDirection))
assert.False(logical.Sorted(entities, tt.targetFieldIdx, reverseSortDirection(tt.sortDirection)))
})
}
}
func reverseSortDirection(sort apiv1.QueryOrder_Sort) apiv1.QueryOrder_Sort {
if sort == apiv1.QueryOrder_SORT_DESC {
return apiv1.QueryOrder_SORT_ASC
}
return apiv1.QueryOrder_SORT_DESC
}