Skip to content

Commit 205a49e

Browse files
authored
Support RDB_TYPE_HASH_2 (Valkey 9) in DumpRdbValueVisitor (#119)
* Support RDB_TYPE_HASH_2 for valkey 9 * Enriched unit tests
1 parent 052f347 commit 205a49e

31 files changed

Lines changed: 1133 additions & 7 deletions

pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,16 @@
428428
</execution>
429429
</executions>
430430
</plugin>
431+
<plugin>
432+
<groupId>org.apache.maven.plugins</groupId>
433+
<artifactId>maven-surefire-plugin</artifactId>
434+
<configuration>
435+
<!-- Run tests on the classpath instead of the module path so JUnit
436+
can reflect on @Rule fields without requiring `opens` directives
437+
in module-info.java. -->
438+
<useModulePath>false</useModulePath>
439+
</configuration>
440+
</plugin>
431441
</plugins>
432442
</build>
433443
</project>

src/main/java/com/moilioncircle/redis/replicator/AbstractReplicator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,8 @@ public void builtInCommandParserRegister() {
347347
addCommandParser(CommandName.name("XDELEX"), new XDelExParser());
348348
// since redis 8.4
349349
addCommandParser(CommandName.name("MSETEX"), new MSetExParser());
350+
// flavor-specific parsers (e.g., Valkey 9 hash field TTL)
351+
configuration.getFlavor().extendCommandParsers(this);
350352
}
351353

352354
@Override

src/main/java/com/moilioncircle/redis/replicator/Constants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ private Constants() {
9898
public static final int RDB_TYPE_STREAM_LISTPACKS_2 = 19;
9999
public static final int RDB_TYPE_SET_LISTPACK = 20; /* since redis 7.2 */
100100
public static final int RDB_TYPE_STREAM_LISTPACKS_3 = 21; /* since redis 7.2 */
101+
public static final int RDB_TYPE_HASH_2 = 22; /* valkey 9, hash with field-level expiration */
101102
public static final int RDB_TYPE_HASH_METADATA = 24; /* since redis 7.4 */
102103
public static final int RDB_TYPE_HASH_LISTPACK_EX = 25; /* since redis 7.4 */
103104

src/main/java/com/moilioncircle/redis/replicator/Flavor.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
import java.util.HashMap;
2020
import java.util.Map;
2121

22+
import com.moilioncircle.redis.replicator.cmd.CommandName;
23+
import com.moilioncircle.redis.replicator.cmd.parser.HExpireAtParser;
24+
import com.moilioncircle.redis.replicator.cmd.parser.HExpireParser;
25+
import com.moilioncircle.redis.replicator.cmd.parser.HPExpireParser;
2226
import com.moilioncircle.redis.replicator.rdb.DefaultRdbVisitor;
2327
import com.moilioncircle.redis.replicator.rdb.RdbVisitor;
2428

@@ -68,6 +72,14 @@ public boolean isValidRdbVersion(int version) {
6872
public RdbVisitor rdbVisitor(Replicator replicator) {
6973
return new DefaultRdbVisitor(replicator);
7074
}
75+
76+
@Override
77+
public void extendCommandParsers(Replicator replicator) {
78+
// since valkey 9
79+
replicator.addCommandParser(CommandName.name("HEXPIRE"), new HExpireParser());
80+
replicator.addCommandParser(CommandName.name("HPEXPIRE"), new HPExpireParser());
81+
replicator.addCommandParser(CommandName.name("HEXPIREAT"), new HExpireAtParser());
82+
}
7183
};
7284

7385
public static Flavor toFlavor(String flavor) {

src/main/java/com/moilioncircle/redis/replicator/FlavorSupport.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ public interface FlavorSupport {
3030
boolean isValidRdbVersion(int version);
3131

3232
RdbVisitor rdbVisitor(Replicator replicator);
33-
33+
34+
default void extendCommandParsers(Replicator replicator) {
35+
}
36+
3437
default String prepend(String suffix) {
3538
return magic().toLowerCase() + suffix;
3639
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2026 otheng03
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.moilioncircle.redis.replicator.cmd.impl;
18+
19+
import com.moilioncircle.redis.replicator.cmd.CommandSpec;
20+
21+
/**
22+
* @author otheng03
23+
* @since 3.12.0
24+
*/
25+
@CommandSpec(command = "HEXPIREAT")
26+
public class HExpireAtCommand extends GenericKeyCommand {
27+
28+
private static final long serialVersionUID = 1L;
29+
30+
private long ex;
31+
32+
private byte[][] fields;
33+
34+
private ExistType existType;
35+
36+
private CompareType compareType;
37+
38+
public HExpireAtCommand() {
39+
}
40+
41+
public HExpireAtCommand(byte[] key, byte[][] fields, long ex, ExistType existType, CompareType compareType) {
42+
super(key);
43+
this.fields = fields;
44+
this.ex = ex;
45+
this.existType = existType;
46+
this.compareType = compareType;
47+
}
48+
49+
public long getEx() {
50+
return ex;
51+
}
52+
53+
public void setEx(long ex) {
54+
this.ex = ex;
55+
}
56+
57+
public byte[][] getFields() {
58+
return fields;
59+
}
60+
61+
public void setFields(byte[][] fields) {
62+
this.fields = fields;
63+
}
64+
65+
public ExistType getExistType() {
66+
return existType;
67+
}
68+
69+
public void setExistType(ExistType existType) {
70+
this.existType = existType;
71+
}
72+
73+
public CompareType getCompareType() {
74+
return compareType;
75+
}
76+
77+
public void setCompareType(CompareType compareType) {
78+
this.compareType = compareType;
79+
}
80+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2026 otheng03
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.moilioncircle.redis.replicator.cmd.impl;
18+
19+
import com.moilioncircle.redis.replicator.cmd.CommandSpec;
20+
21+
/**
22+
* @author otheng03
23+
* @since 3.12.0
24+
*/
25+
@CommandSpec(command = "HEXPIRE")
26+
public class HExpireCommand extends GenericKeyCommand {
27+
28+
private static final long serialVersionUID = 1L;
29+
30+
private long ex;
31+
32+
private byte[][] fields;
33+
34+
private ExistType existType;
35+
36+
private CompareType compareType;
37+
38+
public HExpireCommand() {
39+
}
40+
41+
public HExpireCommand(byte[] key, byte[][] fields, long ex, ExistType existType, CompareType compareType) {
42+
super(key);
43+
this.fields = fields;
44+
this.ex = ex;
45+
this.existType = existType;
46+
this.compareType = compareType;
47+
}
48+
49+
public long getEx() {
50+
return ex;
51+
}
52+
53+
public void setEx(long ex) {
54+
this.ex = ex;
55+
}
56+
57+
public byte[][] getFields() {
58+
return fields;
59+
}
60+
61+
public void setFields(byte[][] fields) {
62+
this.fields = fields;
63+
}
64+
65+
public ExistType getExistType() {
66+
return existType;
67+
}
68+
69+
public void setExistType(ExistType existType) {
70+
this.existType = existType;
71+
}
72+
73+
public CompareType getCompareType() {
74+
return compareType;
75+
}
76+
77+
public void setCompareType(CompareType compareType) {
78+
this.compareType = compareType;
79+
}
80+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2026 otheng03
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.moilioncircle.redis.replicator.cmd.impl;
18+
19+
import com.moilioncircle.redis.replicator.cmd.CommandSpec;
20+
21+
/**
22+
* @author otheng03
23+
* @since 3.12.0
24+
*/
25+
@CommandSpec(command = "HPEXPIRE")
26+
public class HPExpireCommand extends GenericKeyCommand {
27+
28+
private static final long serialVersionUID = 1L;
29+
30+
private long ex;
31+
32+
private byte[][] fields;
33+
34+
private ExistType existType;
35+
36+
private CompareType compareType;
37+
38+
public HPExpireCommand() {
39+
}
40+
41+
public HPExpireCommand(byte[] key, byte[][] fields, long ex, ExistType existType, CompareType compareType) {
42+
super(key);
43+
this.fields = fields;
44+
this.ex = ex;
45+
this.existType = existType;
46+
this.compareType = compareType;
47+
}
48+
49+
public long getEx() {
50+
return ex;
51+
}
52+
53+
public void setEx(long ex) {
54+
this.ex = ex;
55+
}
56+
57+
public byte[][] getFields() {
58+
return fields;
59+
}
60+
61+
public void setFields(byte[][] fields) {
62+
this.fields = fields;
63+
}
64+
65+
public ExistType getExistType() {
66+
return existType;
67+
}
68+
69+
public void setExistType(ExistType existType) {
70+
this.existType = existType;
71+
}
72+
73+
public CompareType getCompareType() {
74+
return compareType;
75+
}
76+
77+
public void setCompareType(CompareType compareType) {
78+
this.compareType = compareType;
79+
}
80+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2026 otheng03
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.moilioncircle.redis.replicator.cmd.parser;
18+
19+
import static com.moilioncircle.redis.replicator.cmd.CommandParsers.toBytes;
20+
import static com.moilioncircle.redis.replicator.cmd.CommandParsers.toLong;
21+
import static com.moilioncircle.redis.replicator.cmd.CommandParsers.toRune;
22+
import static com.moilioncircle.redis.replicator.util.Strings.isEquals;
23+
24+
import com.moilioncircle.redis.replicator.cmd.CommandParser;
25+
import com.moilioncircle.redis.replicator.cmd.impl.CompareType;
26+
import com.moilioncircle.redis.replicator.cmd.impl.ExistType;
27+
import com.moilioncircle.redis.replicator.cmd.impl.HExpireAtCommand;
28+
29+
/**
30+
* @author otheng03
31+
* @since 3.12.0
32+
*/
33+
public class HExpireAtParser implements CommandParser<HExpireAtCommand> {
34+
35+
@Override
36+
public HExpireAtCommand parse(Object[] command) {
37+
int idx = 1;
38+
byte[] key = toBytes(command[idx]);
39+
idx++;
40+
long ex = toLong(command[idx++]);
41+
42+
ExistType existType = ExistType.NONE;
43+
CompareType compareType = CompareType.NONE;
44+
while (idx < command.length) {
45+
String param = toRune(command[idx]);
46+
if (isEquals(param, "NX")) {
47+
existType = ExistType.NX;
48+
} else if (isEquals(param, "XX")) {
49+
existType = ExistType.XX;
50+
} else if (isEquals(param, "GT")) {
51+
compareType = CompareType.GT;
52+
} else if (isEquals(param, "LT")) {
53+
compareType = CompareType.LT;
54+
} else if (isEquals(param, "FIELDS")) {
55+
break;
56+
}
57+
idx++;
58+
}
59+
60+
idx += 2; // skip FIELDS numFields
61+
byte[][] fields = new byte[command.length - idx][];
62+
for (int i = idx, j = 0; i < command.length; i++, j++) {
63+
fields[j] = toBytes(command[i]);
64+
}
65+
return new HExpireAtCommand(key, fields, ex, existType, compareType);
66+
}
67+
68+
}

0 commit comments

Comments
 (0)