Skip to content

Conversation

@hanxizh9910
Copy link
Contributor

Summary

Addresses #2619.
This PR extends the HSETEX command to support optional key-level NX and XX flags, allowing operations conditional on the existence of the hash key.

Changes

  • Updated hsetex.json and regenerated commands.def.
  • Extended argument parsing for NX/XX.
  • Added key-level NX/XX support in HSETEX.
  • Added tests covering all four NX/XX scenarios.

@codecov
Copy link

codecov bot commented Sep 30, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.47%. Comparing base (d29d580) to head (c1673e1).
⚠️ Report is 23 commits behind head on unstable.

Additional details and impacted files
@@             Coverage Diff              @@
##           unstable    #2668      +/-   ##
============================================
- Coverage     72.57%   72.47%   -0.10%     
============================================
  Files           128      128              
  Lines         71277    70171    -1106     
============================================
- Hits          51727    50857     -870     
+ Misses        19550    19314     -236     
Files with missing lines Coverage Δ
src/commands.def 100.00% <ø> (ø)
src/server.c 88.41% <ø> (-0.02%) ⬇️
src/t_hash.c 96.16% <100.00%> (-0.09%) ⬇️

... and 102 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@madolson madolson moved this to Todo in Valkey 9.1 Oct 1, 2025
@ranshid
Copy link
Member

ranshid commented Oct 1, 2025

Thank you @hanxizh9910 ! I am starting a holiday so will try to start review next week (sorry for that)

Copy link
Member

@ranshid ranshid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left some small test comments.

One thing that I just thought of is the forward compatibility. Since we are adding this in 9.1 it will make it problematic to sync commands withy the new NX/XX arguments as it is persisted on the replication stream.
Before that we kept the FXX/FNX for simplicity, but I think that we should now rewrite the command vector in case either FXX/FNX/NX/XX are included to prevent some weird replication misalignments and support replicating to 9.0.

@zuiderkwast FYI

@zuiderkwast
Copy link
Contributor

Before that we kept the FXX/FNX for simplicity, but I think that we should now rewrite the command vector in case either FXX/FNX/NX/XX are included to prevent some weird replication misalignments and support replicating to 9.0.

Technically, NX and XX are new parameters in 9.1, so by starting to use them, the user opts in to the new feature. Using a new feature in the new version means that the possibility to downgrade ends. With this reasoning, it should be OK to just add them and replicate them. That said, I think it's cleaner to rewrite the command and replicate without them.

@ranshid
Copy link
Member

ranshid commented Oct 8, 2025

Before that we kept the FXX/FNX for simplicity, but I think that we should now rewrite the command vector in case either FXX/FNX/NX/XX are included to prevent some weird replication misalignments and support replicating to 9.0.

Technically, NX and XX are new parameters in 9.1, so by starting to use them, the user opts in to the new feature. Using a new feature in the new version means that the possibility to downgrade ends. With this reasoning, it should be OK to just add them and replicate them. That said, I think it's cleaner to rewrite the command and replicate without them.

Agree, but I think we should take extra care to try and avoid breaking this in minor version.

@ranshid
Copy link
Member

ranshid commented Oct 10, 2025

@hanxizh9910 thank you for spotting the bug in HSETEX with FXX!

However I suspect we can fix that in the upcoming 9.0 GA (it is a trivial fix IMO) and it will prevent leaving an empty key which can cause trouble.
Let me drive a separate PR and you can rebase this in case it will be taken to 9.0

@ranshid
Copy link
Member

ranshid commented Oct 10, 2025

here: #2716

Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>

Generated new commands.def

Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>

Extended parseExtendedCommandArgumentsOrReply function to handle nx and xx for COMMAND_HSET

Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>

Added logic in hsetexCommand function to handle NX/XX

Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>

Fix: removed two spaces to pass the format test'

Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>

Added tests for nx/xx in hsetex command

Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>
Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>
Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>
Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>
Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>
Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>
@hanxizh9910 hanxizh9910 force-pushed the feature/hsetex-nx-xx-support branch from 48584bb to d8bcc1f Compare October 10, 2025 18:36
Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>
Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>
@hanxizh9910
Copy link
Contributor Author

@hanxizh9910 thank you for spotting the bug in HSETEX with FXX!

However I suspect we can fix that in the upcoming 9.0 GA (it is a trivial fix IMO) and it will prevent leaving an empty key which can cause trouble. Let me drive a separate PR and you can rebase this in case it will be taken to 9.0

@ranshid , thank you for your feedback! I got the changes you made!

@hanxizh9910
Copy link
Contributor Author

Hi @ranshid, I made a commit to attempt rewriting the command. Could you please let me know if this is what you had in mind. Also, do you want this rewrite change included in this PR? Thank you!

src/t_hash.c Outdated
Comment on lines 1314 to 1343
/* Rewrite command vector for replication */
if (flags & (ARGS_SET_NX | ARGS_SET_XX | ARGS_SET_FNX | ARGS_SET_FXX)) {
int rewrite_argc = 2 + num_fields * 2; // command, hash + field/value pairs
robj **rewrite_argv = zmalloc(sizeof(robj *) * rewrite_argc);
int j = 0;

// First argument: command
rewrite_argv[j++] = c->argv[0];
incrRefCount(c->argv[0]);

// Second argument: hash
rewrite_argv[j++] = c->argv[1];
incrRefCount(c->argv[1]);

// Copy all field/value pairs, skipping NX/XX/FNX/FXX flags
for (int i = fields_index; i < c->argc; i++) {
// Skip NX/XX/FNX/FXX flags
if (strcmp(c->argv[i]->ptr, "NX") &&
strcmp(c->argv[i]->ptr, "XX") &&
strcmp(c->argv[i]->ptr, "FNX") &&
strcmp(c->argv[i]->ptr, "FXX")) {
rewrite_argv[j++] = c->argv[i];
incrRefCount(c->argv[i]);
}
}

// Replace client command vector
replaceClientCommandVector(c, j, rewrite_argv);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might work, but it is somewhat wasteful. For example, when we are expiring elements (eg expiration time is zero) we already managing a separate argv. I think maybe it would be better to simply use and manage a new_argv when we are doing the hashTypeSet. We can optimize by not adding the parameters when the new_argv is NULL.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ranshid, thanks for the feedback! I’ve updated the function to use new_argv. Is this what you had in mind?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ranshid, thanks for the feedback! I’ve updated the function to use new_argv. Is this what you had in mind?

Thank you @hanxizh9910.
Not exactly what I meant is that we might refactor the function to:

  1. we can set an early mark that we need to use a new argv in case flags & (ARGS_SET_NX | ARGS_SET_XX | ARGS_SET_FNX | ARGS_SET_FXX))
  2. Then we can allocate the relevant new_argv as an else case for the if condition in line 1263, like:
if (set_expired) {
        new_argv = zmalloc(sizeof(robj *) * (num_fields + 2));
        new_argv[new_argc++] = shared.hdel;
        incrRefCount(shared.hdel);
        new_argv[new_argc++] = c->argv[1];
        incrRefCount(c->argv[1]);
 } else if (flags & (ARGS_SET_NX | ARGS_SET_XX | ARGS_SET_FNX | ARGS_SET_FXX)) ) {
      new_argv = zmalloc(sizeof(robj *) * (num_fields + 2));
      ....
}
  1. We should extend the code in line 1281 to manage the new_argv:
 } else {
            hashTypeSet(o, c->argv[i]->ptr, c->argv[i + 1]->ptr, when, set_flags);
            changes++;
            if (flags & (ARGS_SET_NX | ARGS_SET_XX | ARGS_SET_FNX | ARGS_SET_FXX))) {
              new_argv[new_argc++] = c->argv[i];
              incrRefCount(c->argv[i]);
            }
 }
  1. rewrite the command vector after we notify the keyspace event in line 1296

This might need to also handle the PXAT conversion differently (maybe earlier?) but that was my suggestion.

For your suggestion:

  1. this if should probably be placed under the previous else in line 1225, since when set_expired is true, we already rewrite the command differently without these values.
  2. there is no need to compare each field but only fields in index 2 and up to fields_index

I guess we could also use your suggestion as well (with the 2 fixes I listed before).

Copy link
Contributor Author

@hanxizh9910 hanxizh9910 Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ranshid, thank you for your suggestion! I’ve modified the code accordingly, does this match what you had in mind?

Also, I have a question about the PXAT conversion: if no fields are modified due to NX/XX/FNX/FXX, should we still update the expiration time?

Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>
…w_argc, new_argv) incorrectly before

Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>
Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>
Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>
Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>
Signed-off-by: Hanxi Zhang <hanxizh@amazon.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

3 participants