Files
signoz/pkg/statementbuilder/tracesstatementbuilder/trace_time_range_test.go
Tushar Vats e2e7caf1ca refactor(querier): 3-layer per-signal query architecture (#12304)
Split the five telemetry<signal> packages, which mixed three concerns, into
three per-signal layers with a cycle-free dependency direction:

- telemetryschema/<signal>telemetryschema — primitives (const + table
  selection, field_mapper, condition_builder, trace helpers); leaf layer.
- statementbuilder/<signal>statementbuilder — SQL generation. The parent
  statementbuilder package is contract-only (the Builders bundle + Config);
  each sub-package exposes a factory.ProviderFactory[..., statementbuilder.Config]
  whose New internalizes FieldMapper/ConditionBuilder/AggExprRewriter and reads
  SkipResourceFingerprint. Traces exposes two factories (query + operator).
  telemetryresourcefilter moves here as statementbuilder/resourcefilter.
- telemetrymetadata — key/value resolution; NewTelemetryMetaStore collapses
  from 24 args to (settings, telemetrystore, flagger), sourcing table names
  from the schema constants.

Centralize query-stack assembly in signoz.go via newQueryStack: build the
single metadata store, run each per-signal statement-builder factory, assemble
the statementbuilder.Builders bundle, and build the bucket cache — once. This
is the only place that imports the concrete sub-packages (so the edge runs
subs -> parent, cycle-free), and it removes the duplicate metadata store that
signozquerier used to build, leaving signozquerier a thin
querier.New(*statementbuilder.Builders) adapter.

Also:
- statementbuilder.Config owns SkipResourceFingerprint (moved off
  querier.Config). YAML key moves querier.skip_resource_fingerprint ->
  statementbuilder.skip_resource_fingerprint.
- Querier interface moves into querier.go (interfaces.go removed); BucketCache
  -> bucket_cache.go, Handler -> api.go.
- Add pkg/querier/queriertest.MockQuerier.
2026-07-29 09:24:08 +00:00

175 lines
5.2 KiB
Go

package tracesstatementbuilder
import (
"context"
"strings"
"testing"
"time"
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
"github.com/SigNoz/signoz/pkg/querybuilder"
"github.com/SigNoz/signoz/pkg/telemetryschema/tracestelemetryschema"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes/telemetrytypestest"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestTraceTimeRangeOptimization(t *testing.T) {
releaseTime := time.Date(2025, 5, 22, 22, 0, 0, 0, time.UTC)
fm := tracestelemetryschema.NewFieldMapper()
cb := tracestelemetryschema.NewConditionBuilder(fm)
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
mockMetadataStore.KeysMap = tracestelemetryschema.BuildCompleteFieldKeyMap(releaseTime)
mockMetadataStore.KeysMap["trace_id"] = []*telemetrytypes.TelemetryFieldKey{{
Name: "trace_id",
FieldContext: telemetrytypes.FieldContextSpan,
FieldDataType: telemetrytypes.FieldDataTypeString,
Signal: telemetrytypes.SignalTraces,
}}
mockMetadataStore.KeysMap["name"] = []*telemetrytypes.TelemetryFieldKey{{
Name: "name",
FieldContext: telemetrytypes.FieldContextSpan,
FieldDataType: telemetrytypes.FieldDataTypeString,
Signal: telemetrytypes.SignalTraces,
}}
fl := flaggertest.New(t)
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, fl)
statementBuilder := NewTraceQueryStatementBuilder(
instrumentationtest.New().ToProviderSettings(),
mockMetadataStore,
fm,
cb,
aggExprRewriter,
nil, // telemetryStore is nil - adaptive path is disabled
fl,
false,
100000,
)
tests := []struct {
name string
query qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]
expectTimeOptimization bool
}{
{
name: "query with trace_id filter",
query: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
Signal: telemetrytypes.SignalTraces,
Filter: &qbtypes.Filter{
Expression: "trace_id = '12345abc' AND service.name = 'api'",
},
SelectFields: []telemetrytypes.TelemetryFieldKey{
{Name: "name", FieldContext: telemetrytypes.FieldContextSpan},
},
},
expectTimeOptimization: true, // would optimize if telemetryStore was provided
},
{
name: "query with trace_id IN filter",
query: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
Signal: telemetrytypes.SignalTraces,
Filter: &qbtypes.Filter{
Expression: "trace_id IN ['12345abc', '67890def'] AND service.name = 'api'",
},
SelectFields: []telemetrytypes.TelemetryFieldKey{
{Name: "name", FieldContext: telemetrytypes.FieldContextSpan},
},
},
expectTimeOptimization: true, // would optimize if telemetryStore was provided
},
{
name: "query without trace_id filter",
query: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
Signal: telemetrytypes.SignalTraces,
Filter: &qbtypes.Filter{
Expression: "service.name = 'api'",
},
SelectFields: []telemetrytypes.TelemetryFieldKey{
{Name: "name", FieldContext: telemetrytypes.FieldContextSpan},
},
},
expectTimeOptimization: false,
},
{
name: "query with empty filter",
query: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
Signal: telemetrytypes.SignalTraces,
SelectFields: []telemetrytypes.TelemetryFieldKey{
{Name: "name", FieldContext: telemetrytypes.FieldContextSpan},
},
},
expectTimeOptimization: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
stmt, err := statementBuilder.Build(
ctx,
valuer.UUID{},
1747947419000, // start time in ms
1747983448000, // end time in ms
qbtypes.RequestTypeRaw,
tt.query,
nil,
)
require.NoError(t, err)
require.NotNil(t, stmt)
assert.NotEmpty(t, stmt.Query)
if tt.query.Filter != nil && tt.query.Filter.Expression != "" {
traceIDs, found := tracestelemetryschema.ExtractTraceIDsFromFilter(tt.query.Filter.Expression)
assert.Equal(t, tt.expectTimeOptimization, found && len(traceIDs) > 0)
}
})
}
}
func TestTraceTimeRangeFinderQuery(t *testing.T) {
expectedQuery := `
SELECT
toUnixTimestamp64Nano(min(timestamp)) as start_time,
toUnixTimestamp64Nano(max(timestamp)) as end_time
FROM signoz_traces.distributed_signoz_spans
WHERE traceID = ?
AND timestamp >= now() - INTERVAL 30 DAY
`
expectedQuery = normalizeQuery(expectedQuery)
actualQuery := `
SELECT
toUnixTimestamp64Nano(min(timestamp)) as start_time,
toUnixTimestamp64Nano(max(timestamp)) as end_time
FROM signoz_traces.distributed_signoz_spans
WHERE traceID = ?
AND timestamp >= now() - INTERVAL 30 DAY
`
actualQuery = normalizeQuery(actualQuery)
assert.Equal(t, expectedQuery, actualQuery)
}
func normalizeQuery(query string) string {
lines := []string{}
for _, line := range strings.Split(strings.TrimSpace(query), "\n") {
line = strings.TrimSpace(line)
if line != "" {
lines = append(lines, line)
}
}
return strings.Join(lines, " ")
}