Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,16 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- Run tests on the classpath instead of the module path so JUnit
can reflect on @Rule fields without requiring `opens` directives
in module-info.java. -->
<useModulePath>false</useModulePath>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,8 @@ public void builtInCommandParserRegister() {
addCommandParser(CommandName.name("XDELEX"), new XDelExParser());
// since redis 8.4
addCommandParser(CommandName.name("MSETEX"), new MSetExParser());
// flavor-specific parsers (e.g., Valkey 9 hash field TTL)
configuration.getFlavor().extendCommandParsers(this);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ private Constants() {
public static final int RDB_TYPE_STREAM_LISTPACKS_2 = 19;
public static final int RDB_TYPE_SET_LISTPACK = 20; /* since redis 7.2 */
public static final int RDB_TYPE_STREAM_LISTPACKS_3 = 21; /* since redis 7.2 */
public static final int RDB_TYPE_HASH_2 = 22; /* valkey 9, hash with field-level expiration */
public static final int RDB_TYPE_HASH_METADATA = 24; /* since redis 7.4 */
public static final int RDB_TYPE_HASH_LISTPACK_EX = 25; /* since redis 7.4 */

Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/moilioncircle/redis/replicator/Flavor.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
import java.util.HashMap;
import java.util.Map;

import com.moilioncircle.redis.replicator.cmd.CommandName;
import com.moilioncircle.redis.replicator.cmd.parser.HExpireAtParser;
import com.moilioncircle.redis.replicator.cmd.parser.HExpireParser;
import com.moilioncircle.redis.replicator.cmd.parser.HPExpireParser;
import com.moilioncircle.redis.replicator.rdb.DefaultRdbVisitor;
import com.moilioncircle.redis.replicator.rdb.RdbVisitor;

Expand Down Expand Up @@ -68,6 +72,14 @@ public boolean isValidRdbVersion(int version) {
public RdbVisitor rdbVisitor(Replicator replicator) {
return new DefaultRdbVisitor(replicator);
}

@Override
public void extendCommandParsers(Replicator replicator) {
// since valkey 9
replicator.addCommandParser(CommandName.name("HEXPIRE"), new HExpireParser());
replicator.addCommandParser(CommandName.name("HPEXPIRE"), new HPExpireParser());
replicator.addCommandParser(CommandName.name("HEXPIREAT"), new HExpireAtParser());
}
};

public static Flavor toFlavor(String flavor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ public interface FlavorSupport {
boolean isValidRdbVersion(int version);

RdbVisitor rdbVisitor(Replicator replicator);


default void extendCommandParsers(Replicator replicator) {
}

default String prepend(String suffix) {
return magic().toLowerCase() + suffix;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2026 otheng03
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.moilioncircle.redis.replicator.cmd.impl;

import com.moilioncircle.redis.replicator.cmd.CommandSpec;

/**
* @author otheng03
* @since 3.12.0
*/
@CommandSpec(command = "HEXPIREAT")
public class HExpireAtCommand extends GenericKeyCommand {

private static final long serialVersionUID = 1L;

private long ex;

private byte[][] fields;

private ExistType existType;

private CompareType compareType;

public HExpireAtCommand() {
}

public HExpireAtCommand(byte[] key, byte[][] fields, long ex, ExistType existType, CompareType compareType) {
super(key);
this.fields = fields;
this.ex = ex;
this.existType = existType;
this.compareType = compareType;
}

public long getEx() {
return ex;
}

public void setEx(long ex) {
this.ex = ex;
}

public byte[][] getFields() {
return fields;
}

public void setFields(byte[][] fields) {
this.fields = fields;
}

public ExistType getExistType() {
return existType;
}

public void setExistType(ExistType existType) {
this.existType = existType;
}

public CompareType getCompareType() {
return compareType;
}

public void setCompareType(CompareType compareType) {
this.compareType = compareType;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2026 otheng03
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.moilioncircle.redis.replicator.cmd.impl;

import com.moilioncircle.redis.replicator.cmd.CommandSpec;

/**
* @author otheng03
* @since 3.12.0
*/
@CommandSpec(command = "HEXPIRE")
public class HExpireCommand extends GenericKeyCommand {

private static final long serialVersionUID = 1L;

private long ex;

private byte[][] fields;

private ExistType existType;

private CompareType compareType;

public HExpireCommand() {
}

public HExpireCommand(byte[] key, byte[][] fields, long ex, ExistType existType, CompareType compareType) {
super(key);
this.fields = fields;
this.ex = ex;
this.existType = existType;
this.compareType = compareType;
}

public long getEx() {
return ex;
}

public void setEx(long ex) {
this.ex = ex;
}

public byte[][] getFields() {
return fields;
}

public void setFields(byte[][] fields) {
this.fields = fields;
}

public ExistType getExistType() {
return existType;
}

public void setExistType(ExistType existType) {
this.existType = existType;
}

public CompareType getCompareType() {
return compareType;
}

public void setCompareType(CompareType compareType) {
this.compareType = compareType;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2026 otheng03
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.moilioncircle.redis.replicator.cmd.impl;

import com.moilioncircle.redis.replicator.cmd.CommandSpec;

/**
* @author otheng03
* @since 3.12.0
*/
@CommandSpec(command = "HPEXPIRE")
public class HPExpireCommand extends GenericKeyCommand {

private static final long serialVersionUID = 1L;

private long ex;

private byte[][] fields;

private ExistType existType;

private CompareType compareType;

public HPExpireCommand() {
}

public HPExpireCommand(byte[] key, byte[][] fields, long ex, ExistType existType, CompareType compareType) {
super(key);
this.fields = fields;
this.ex = ex;
this.existType = existType;
this.compareType = compareType;
}

public long getEx() {
return ex;
}

public void setEx(long ex) {
this.ex = ex;
}

public byte[][] getFields() {
return fields;
}

public void setFields(byte[][] fields) {
this.fields = fields;
}

public ExistType getExistType() {
return existType;
}

public void setExistType(ExistType existType) {
this.existType = existType;
}

public CompareType getCompareType() {
return compareType;
}

public void setCompareType(CompareType compareType) {
this.compareType = compareType;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2026 otheng03
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.moilioncircle.redis.replicator.cmd.parser;

import static com.moilioncircle.redis.replicator.cmd.CommandParsers.toBytes;
import static com.moilioncircle.redis.replicator.cmd.CommandParsers.toLong;
import static com.moilioncircle.redis.replicator.cmd.CommandParsers.toRune;
import static com.moilioncircle.redis.replicator.util.Strings.isEquals;

import com.moilioncircle.redis.replicator.cmd.CommandParser;
import com.moilioncircle.redis.replicator.cmd.impl.CompareType;
import com.moilioncircle.redis.replicator.cmd.impl.ExistType;
import com.moilioncircle.redis.replicator.cmd.impl.HExpireAtCommand;

/**
* @author otheng03
* @since 3.12.0
*/
public class HExpireAtParser implements CommandParser<HExpireAtCommand> {

@Override
public HExpireAtCommand parse(Object[] command) {
int idx = 1;
byte[] key = toBytes(command[idx]);
idx++;
long ex = toLong(command[idx++]);

ExistType existType = ExistType.NONE;
CompareType compareType = CompareType.NONE;
while (idx < command.length) {
String param = toRune(command[idx]);
if (isEquals(param, "NX")) {
existType = ExistType.NX;
} else if (isEquals(param, "XX")) {
existType = ExistType.XX;
} else if (isEquals(param, "GT")) {
compareType = CompareType.GT;
} else if (isEquals(param, "LT")) {
compareType = CompareType.LT;
} else if (isEquals(param, "FIELDS")) {
break;
}
idx++;
}

idx += 2; // skip FIELDS numFields
byte[][] fields = new byte[command.length - idx][];
for (int i = idx, j = 0; i < command.length; i++, j++) {
fields[j] = toBytes(command[i]);
}
return new HExpireAtCommand(key, fields, ex, existType, compareType);
}

}
Loading
Loading