mirror of
https://github.com/redis/go-redis.git
synced 2026-10-24 07:59:20 +03:00
* feat(timeseries): add TS.QUERYLABELS support (Redis 8.10)
- TSQueryLabels lists label names, TSQueryLabelValues the values of
one label, across series matching optional TS.QUERYINDEX-style
filters
- the FILTER token is emitted only when expressions are given (the
server rejects a bare FILTER); expressions pass through verbatim
and the VALUES label is matched byte-exactly
- replies are unordered, server-deduplicated string sets under both
RESP2 and RESP3; empty replies are success, not errors
- wire-format unit tests plus a RESP2/RESP3 integration test that
gates on the live server supporting the command
- runnable example under example/ts-querylabels
* fix(tests): use skip for tests
* test(timeseries): version-gate TS.QUERYLABELS
Replace the standalone probe test (command probing plus the
REDIS_QUERYLABELS_TEST_ADDR env var) with Ginkgo specs in the main
suite, gated on SkipBeforeRedisVersion("8.10"). The plain go test also
called the Ginkgo-based SkipBeforeRedisVersion helper, which panics
outside the suite when it actually skips; moving the specs into the
suite fixes that. Covers both command forms under RESP2 and RESP3.
* chore(deps): bump grpc to v1.82.1 in otel examples
govulncheck fails every PR on GO-2026-6061 in google.golang.org/grpc
v1.80.0 (indirect via the OTLP exporters); v1.82.1 carries the fix.
---------
Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com>
76 lines
2.4 KiB
Go
76 lines
2.4 KiB
Go
package redis
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
// TestTSQueryLabelsArgs pins the wire format of both TS.QUERYLABELS forms.
|
|
// The FILTER token must be emitted only when at least one filter expression
|
|
// is given (the server rejects a bare FILTER), filter expressions must be
|
|
// passed verbatim and in order, and the VALUES label must not be normalized.
|
|
func TestTSQueryLabelsArgs(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
tests := []struct {
|
|
name string
|
|
call func(c cmdable) Cmder
|
|
want []interface{}
|
|
}{
|
|
{
|
|
name: "labels_without_filter_omits_filter_token",
|
|
call: func(c cmdable) Cmder { return c.TSQueryLabels(ctx, nil) },
|
|
want: []interface{}{"TS.QUERYLABELS", "LABELS"},
|
|
},
|
|
{
|
|
name: "labels_with_empty_filter_slice_omits_filter_token",
|
|
call: func(c cmdable) Cmder { return c.TSQueryLabels(ctx, []string{}) },
|
|
want: []interface{}{"TS.QUERYLABELS", "LABELS"},
|
|
},
|
|
{
|
|
name: "labels_with_one_filter",
|
|
call: func(c cmdable) Cmder { return c.TSQueryLabels(ctx, []string{"type=sensor"}) },
|
|
want: []interface{}{"TS.QUERYLABELS", "LABELS", "FILTER", "type=sensor"},
|
|
},
|
|
{
|
|
name: "labels_filters_verbatim_and_ordered",
|
|
call: func(c cmdable) Cmder {
|
|
return c.TSQueryLabels(ctx, []string{"type=sensor", "location!=", "l=(a,b)"})
|
|
},
|
|
want: []interface{}{"TS.QUERYLABELS", "LABELS", "FILTER", "type=sensor", "location!=", "l=(a,b)"},
|
|
},
|
|
{
|
|
name: "values_without_filter_omits_filter_token",
|
|
call: func(c cmdable) Cmder { return c.TSQueryLabelValues(ctx, "location", nil) },
|
|
want: []interface{}{"TS.QUERYLABELS", "VALUES", "location"},
|
|
},
|
|
{
|
|
name: "values_with_filters",
|
|
call: func(c cmdable) Cmder {
|
|
return c.TSQueryLabelValues(ctx, "location", []string{"type=sensor"})
|
|
},
|
|
want: []interface{}{"TS.QUERYLABELS", "VALUES", "location", "FILTER", "type=sensor"},
|
|
},
|
|
{
|
|
name: "values_label_not_normalized",
|
|
call: func(c cmdable) Cmder { return c.TSQueryLabelValues(ctx, " LoCaTiOn ", nil) },
|
|
want: []interface{}{"TS.QUERYLABELS", "VALUES", " LoCaTiOn "},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var captured Cmder
|
|
c := captureCmdable(&captured)
|
|
cmd := tt.call(c)
|
|
if cmd == nil {
|
|
t.Fatalf("command builder returned nil")
|
|
}
|
|
if !reflect.DeepEqual(cmd.Args(), tt.want) {
|
|
t.Errorf("args mismatch\n got: %#v\nwant: %#v", cmd.Args(), tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|