Skip to content

Commit 25f6ac7

Browse files
destinyoooosuxb201
authored andcommitted
fix: add Valkey server type detection support
1 parent 947e881 commit 25f6ac7

3 files changed

Lines changed: 160 additions & 3 deletions

File tree

internal/client/func.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package client
22

33
import (
44
"bytes"
5+
"errors"
56
"strings"
67

78
"RedisShake/internal/client/proto"
@@ -26,3 +27,30 @@ func (r *Redis) IsCluster() bool {
2627
reply := r.DoWithStringReply("INFO", "Cluster")
2728
return strings.Contains(reply, "cluster_enabled:1")
2829
}
30+
31+
// ParseServerVersion parses the server info string and returns whether the server is Valkey.
32+
// Returns true if the server is Valkey, false if it's Redis, and an error if neither is found.
33+
func ParseServerVersion(serverInfo string) (isValkey bool, err error) {
34+
for _, line := range strings.Split(serverInfo, "\n") {
35+
line = strings.TrimSpace(line)
36+
if strings.HasPrefix(line, "valkey_version:") {
37+
return true, nil
38+
}
39+
if strings.HasPrefix(line, "redis_version:") {
40+
return false, nil
41+
}
42+
}
43+
return false, errors.New("server version not found in info string")
44+
}
45+
46+
// IsValkey detects whether the connected server is Valkey by checking the server info.
47+
// Returns true if the server is Valkey, false if it's Redis.
48+
func (r *Redis) IsValkey() bool {
49+
reply := r.DoWithStringReply("INFO", "server")
50+
isValkey, err := ParseServerVersion(reply)
51+
if err != nil {
52+
log.Warnf("failed to detect server type: %v, assuming Redis", err)
53+
return false
54+
}
55+
return isValkey
56+
}

internal/client/func_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package client
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestParseServerVersion(t *testing.T) {
8+
tests := []struct {
9+
name string
10+
serverInfo string
11+
want bool
12+
wantErr bool
13+
}{
14+
{
15+
name: "Valkey 9.x",
16+
serverInfo: `# Server
17+
valkey_version:9.0.0
18+
valkey_mode:standalone
19+
os:Linux 5.4.0 x86_64
20+
arch_bits:64
21+
`,
22+
want: true,
23+
wantErr: false,
24+
},
25+
{
26+
name: "Valkey 8.x",
27+
serverInfo: `# Server
28+
valkey_version:8.5.1
29+
valkey_mode:standalone
30+
os:Linux 6.2.0 x86_64
31+
arch_bits:64
32+
`,
33+
want: true,
34+
wantErr: false,
35+
},
36+
{
37+
name: "Redis 7.x",
38+
serverInfo: `# Server
39+
redis_version:7.2.3
40+
redis_mode:standalone
41+
os:Linux 5.4.0 x86_64
42+
arch_bits:64
43+
`,
44+
want: false,
45+
wantErr: false,
46+
},
47+
{
48+
name: "Redis 6.x",
49+
serverInfo: `# Server
50+
redis_version:6.2.14
51+
redis_mode:standalone
52+
os:Linux 5.4.0 x86_64
53+
arch_bits:64
54+
`,
55+
want: false,
56+
wantErr: false,
57+
},
58+
{
59+
name: "Redis 8.0 (future)",
60+
serverInfo: `# Server
61+
redis_version:8.0.0
62+
redis_mode:standalone
63+
os:Linux 5.4.0 x86_64
64+
arch_bits:64
65+
`,
66+
want: false,
67+
wantErr: false,
68+
},
69+
{
70+
name: "Redis with extra spaces in version",
71+
serverInfo: `# Server
72+
redis_version: 7.2.3
73+
redis_mode:standalone
74+
`,
75+
want: false,
76+
wantErr: false,
77+
},
78+
{
79+
name: "Empty info string",
80+
serverInfo: "",
81+
want: false,
82+
wantErr: true,
83+
},
84+
{
85+
name: "No version info",
86+
serverInfo: `# Server
87+
os:Linux 5.4.0 x86_64
88+
arch_bits:64
89+
`,
90+
want: false,
91+
wantErr: true,
92+
},
93+
{
94+
name: "Valkey with multiple lines and extra info",
95+
serverInfo: `# Server
96+
valkey_version:8.5.1
97+
# Memory
98+
used_memory:1234567
99+
# Stats
100+
total_connections_received:100
101+
`,
102+
want: true,
103+
wantErr: false,
104+
},
105+
{
106+
name: "Redis 5.0 (old version)",
107+
serverInfo: `# Server
108+
redis_version:5.0.14
109+
redis_mode:standalone
110+
`,
111+
want: false,
112+
wantErr: false,
113+
},
114+
}
115+
116+
for _, tt := range tests {
117+
t.Run(tt.name, func(t *testing.T) {
118+
got, err := ParseServerVersion(tt.serverInfo)
119+
if (err != nil) != tt.wantErr {
120+
t.Errorf("ParseServerVersion() error = %v, wantErr %v", err, tt.wantErr)
121+
return
122+
}
123+
if got != tt.want {
124+
t.Errorf("ParseServerVersion() = %v, want %v", got, tt.want)
125+
}
126+
})
127+
}
128+
}

internal/reader/scan_standalone_reader.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type scanStandaloneReader struct {
5353
needRestoreChan chan *needRestoreItem
5454
dumpClient *client.Redis
5555
subWG sync.WaitGroup
56+
isValkey bool
5657

5758
stat struct {
5859
Name string `json:"name"`
@@ -206,6 +207,8 @@ func (r *scanStandaloneReader) scan() {
206207
func (r *scanStandaloneReader) dump() {
207208
nowDbId := 0
208209
r.dumpClient = client.NewRedisClient(r.ctx, r.opts.Address, r.opts.Username, r.opts.Password, r.opts.Tls, r.opts.TlsConfig, r.opts.PreferReplica)
210+
r.isValkey = r.dumpClient.IsValkey()
211+
log.Infof("[%s] detected server type: %s", r.stat.Name, map[bool]string{true: "Valkey", false: "Redis"}[r.isValkey])
209212
// Support prefer_replica=true in both Cluster and Standalone mode
210213
if r.opts.PreferReplica {
211214
r.dumpClient.Do("READONLY")
@@ -300,9 +303,7 @@ func (r *scanStandaloneReader) restore() {
300303
"rdb_restore_command_behavior setting may not work correctly for this key.", key, len(dump))
301304
typeByte := dump[0]
302305
anotherReader := strings.NewReader(dump[1 : len(dump)-10])
303-
// TODO: detect if server is Valkey and pass appropriate flag
304-
// For now, assume Redis format (false)
305-
o := types.ParseObject(anotherReader, typeByte, key, false)
306+
o := types.ParseObject(anotherReader, typeByte, key, r.isValkey)
306307
cmdC := o.Rewrite()
307308
for cmd := range cmdC {
308309
e := entry.NewEntry()

0 commit comments

Comments
 (0)