fix(parser): Adds Redis 8.8 fields and makes reply parsers forward-compatible (#3781)

* Adds Redis 8.8 fields and makes reply parsers forward-compatible

* Bump default redis version for tests to 8.8

* Update .github/actions/run-tests/action.yml

* Update .github/workflows/build.yml

---------

Co-authored-by: Nedyalko Dyakov <[email protected]>
This commit is contained in:
Elena Kolevska
2026-04-20 23:03:24 +03:00
committed by GitHub
co-authored by Nedyalko Dyakov
parent ca74fb89ed
commit 8d91e50250
7 changed files with 67 additions and 18 deletions
+2 -1
View File
@@ -47,7 +47,8 @@ runs:
elif [[ -n "$REDIS_VERSION" ]]; then
# Mapping of redis version to redis testing containers
declare -A redis_version_mapping=(
["8.6.x"]="custom-21860421418-debian-amd64"
["8.8.x"]="8.8-m02"
["8.6.x"]="8.6.1"
["8.4.x"]="8.4.0"
["8.2.x"]="8.2.1-pre"
["8.0.x"]="8.0.2"
+4 -1
View File
@@ -18,6 +18,7 @@ jobs:
fail-fast: false
matrix:
redis-version:
- "8.8.x" # Redis CE 8.8
- "8.6.x" # Redis CE 8.6
- "8.4.x" # Redis CE 8.4
- "8.2.x" # Redis CE 8.2
@@ -46,7 +47,8 @@ jobs:
# Mapping of redis version to redis testing containers
declare -A redis_version_mapping=(
["8.6.x"]="custom-21860421418-debian-amd64"
["8.8.x"]="8.8-m02"
["8.6.x"]="8.6.1"
["8.4.x"]="8.4.0"
["8.2.x"]="8.2.1-pre"
["8.0.x"]="8.0.2"
@@ -77,6 +79,7 @@ jobs:
fail-fast: false
matrix:
redis-version:
- "8.8.x" # Redis CE 8.8
- "8.6.x" # Redis CE 8.6
- "8.4.x" # Redis CE 8.4
- "8.2.x" # Redis CE 8.2
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
services:
redis-stack:
image: redislabs/client-libs-test:custom-21860421418-debian-amd64
image: redislabs/client-libs-test:8.8-m02
env:
TLS_ENABLED: no
REDIS_CLUSTER: no
+2 -2
View File
@@ -1,8 +1,8 @@
GO_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | sort)
REDIS_VERSION ?= 8.6
REDIS_VERSION ?= 8.8
RE_CLUSTER ?= false
RCE_DOCKER ?= true
CLIENT_LIBS_TEST_IMAGE ?= redislabs/client-libs-test:custom-21860421418-debian-amd64
CLIENT_LIBS_TEST_IMAGE ?= redislabs/client-libs-test:8.8-m02
docker.start:
export RE_CLUSTER=$(RE_CLUSTER) && \
+50 -9
View File
@@ -2867,7 +2867,10 @@ func (cmd *XInfoConsumersCmd) readReply(rd *proto.Reader) error {
inactive, err = rd.ReadInt()
cmd.val[i].Inactive = time.Duration(inactive) * time.Millisecond
default:
return fmt.Errorf("redis: unexpected content %s in XINFO CONSUMERS reply", key)
// skip unknown fields
if err = rd.DiscardNext(); err != nil {
return err
}
}
if err != nil {
return err
@@ -2996,7 +2999,10 @@ func (cmd *XInfoGroupsCmd) readReply(rd *proto.Reader) error {
group.Lag = -1
}
default:
return fmt.Errorf("redis: unexpected key %q in XINFO GROUPS reply", key)
// skip unknown fields
if err = rd.DiscardNext(); err != nil {
return err
}
}
}
}
@@ -3165,7 +3171,10 @@ func (cmd *XInfoStreamCmd) readReply(rd *proto.Reader) error {
return err
}
default:
return fmt.Errorf("redis: unexpected key %q in XINFO STREAM reply", key)
// skip unknown fields
if err = rd.DiscardNext(); err != nil {
return err
}
}
}
return nil
@@ -3241,6 +3250,7 @@ type XInfoStreamGroup struct {
EntriesRead int64
Lag int64
PelCount int64
NackedCount uint64 // redis version 8.8, number of NACK'd messages in the group
Pending []XInfoStreamGroupPending
Consumers []XInfoStreamConsumer
}
@@ -3385,7 +3395,10 @@ func (cmd *XInfoStreamFullCmd) readReply(rd *proto.Reader) error {
return err
}
default:
return fmt.Errorf("redis: unexpected key %q in XINFO STREAM FULL reply", key)
// skip unknown fields
if err = rd.DiscardNext(); err != nil {
return err
}
}
}
return nil
@@ -3439,6 +3452,11 @@ func readStreamGroups(rd *proto.Reader) ([]XInfoStreamGroup, error) {
if err != nil {
return nil, err
}
case "nacked-count":
group.NackedCount, err = rd.ReadUint()
if err != nil {
return nil, err
}
case "pending":
group.Pending, err = readXInfoStreamGroupPending(rd)
if err != nil {
@@ -3450,7 +3468,10 @@ func readStreamGroups(rd *proto.Reader) ([]XInfoStreamGroup, error) {
return nil, err
}
default:
return nil, fmt.Errorf("redis: unexpected key %q in XINFO STREAM FULL reply", key)
// skip unknown fields
if err = rd.DiscardNext(); err != nil {
return nil, err
}
}
}
@@ -3575,8 +3596,10 @@ func readXInfoStreamConsumers(rd *proto.Reader) ([]XInfoStreamConsumer, error) {
c.Pending = append(c.Pending, p)
}
default:
return nil, fmt.Errorf("redis: unexpected content %s "+
"in XINFO STREAM FULL reply", cKey)
// skip unknown fields
if err = rd.DiscardNext(); err != nil {
return nil, err
}
}
if err != nil {
return nil, err
@@ -6973,6 +6996,9 @@ type ClientInfo struct {
Resp int // redis version 7.0, client RESP protocol version
LibName string // redis version 7.2, client library name
LibVer string // redis version 7.2, client library version
ReadEvents uint64 // redis version 8.8, number of read events processed
AvgPipelineLenSum uint64 // redis version 8.8, sum of pipeline lengths
AvgPipelineLenCnt uint64 // redis version 8.8, count of pipeline operations
}
type ClientInfoCmd struct {
@@ -7153,8 +7179,14 @@ func parseClientInfo(txt string) (info *ClientInfo, err error) {
info.LibVer = val
case "io-thread":
info.IoThread, err = strconv.Atoi(val)
case "read-events":
info.ReadEvents, err = strconv.ParseUint(val, 10, 64)
case "avg-pipeline-len-sum":
info.AvgPipelineLenSum, err = strconv.ParseUint(val, 10, 64)
case "avg-pipeline-len-cnt":
info.AvgPipelineLenCnt, err = strconv.ParseUint(val, 10, 64)
default:
return nil, fmt.Errorf("redis: unexpected client info key(%s)", key)
// skip unknown fields
}
if err != nil {
@@ -7201,6 +7233,9 @@ func (cmd *ClientInfoCmd) Clone() Cmder {
Resp: cmd.val.Resp,
LibName: cmd.val.LibName,
LibVer: cmd.val.LibVer,
ReadEvents: cmd.val.ReadEvents,
AvgPipelineLenSum: cmd.val.AvgPipelineLenSum,
AvgPipelineLenCnt: cmd.val.AvgPipelineLenCnt,
}
}
return &ClientInfoCmd{
@@ -7307,7 +7342,10 @@ func (cmd *ACLLogCmd) readReply(rd *proto.Reader) error {
case "timestamp-last-updated":
entry.TimestampLastUpdated, err = rd.ReadInt()
default:
return fmt.Errorf("redis: unexpected key %q in ACL LOG reply", key)
// skip unknown fields
if err := rd.DiscardNext(); err != nil {
return err
}
}
if err != nil {
@@ -7371,6 +7409,9 @@ func (cmd *ACLLogCmd) Clone() Cmder {
Resp: entry.ClientInfo.Resp,
LibName: entry.ClientInfo.LibName,
LibVer: entry.ClientInfo.LibVer,
ReadEvents: entry.ClientInfo.ReadEvents,
AvgPipelineLenSum: entry.ClientInfo.AvgPipelineLenSum,
AvgPipelineLenCnt: entry.ClientInfo.AvgPipelineLenCnt,
}
}
}
+4 -4
View File
@@ -1,6 +1,6 @@
---
x-default-image: &default-image ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:8.6.0}
x-default-image: &default-image ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:8.8-m02}
services:
redis:
@@ -164,9 +164,9 @@ services:
- PORT=6390
command: ${REDIS_EXTRA_ARGS:---enable-debug-command yes --enable-module-command yes --tls-auth-clients optional --save ""}
ports:
- 6390:6390
- 6391:6391
- 6392:6392
- "6390:6390"
- "6391:6391"
- "6392:6392"
volumes:
- "./dockers/ring:/redis/work"
profiles:
+4
View File
@@ -204,6 +204,7 @@ var _ = Describe("NewFailoverClusterClient PROTO 2", func() {
It("should sentinel cluster PROTO 2", func() {
_ = client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
defer GinkgoRecover()
val, err := client.Do(ctx, "HELLO").Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).Should(ContainElements("proto", int64(2)))
@@ -306,6 +307,7 @@ var _ = Describe("NewFailoverClusterClient", func() {
Expect(err).NotTo(HaveOccurred())
_ = client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
defer GinkgoRecover()
val, err := c.ClientList(ctx).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).Should(ContainSubstring("name=sentinel_cluster_hi"))
@@ -320,6 +322,7 @@ var _ = Describe("NewFailoverClusterClient", func() {
Expect(err).NotTo(HaveOccurred())
_ = client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
defer GinkgoRecover()
clientInfo, err := c.ClientInfo(ctx).Result()
Expect(err).NotTo(HaveOccurred())
Expect(clientInfo.DB).To(Equal(1))
@@ -329,6 +332,7 @@ var _ = Describe("NewFailoverClusterClient", func() {
It("should sentinel cluster PROTO 3", func() {
_ = client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
defer GinkgoRecover()
val, err := client.Do(ctx, "HELLO").Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).Should(HaveKeyWithValue("proto", int64(3)))