From d9bc5c7a517387d5d006d1e3e4d88d2c3683b02c Mon Sep 17 00:00:00 2001 From: tanruixiang <819464715@qq.com> Date: Thu, 20 Oct 2022 20:04:25 +0800 Subject: [PATCH 1/4] align expire option behavior --- src/commands/redis_cmd.cc | 23 ++- .../gocase/unit/type/strings/strings_test.go | 169 +++++++++++++++++- 2 files changed, 187 insertions(+), 5 deletions(-) diff --git a/src/commands/redis_cmd.cc b/src/commands/redis_cmd.cc index 7bc0b0a8373..f2d8af7cf3c 100644 --- a/src/commands/redis_cmd.cc +++ b/src/commands/redis_cmd.cc @@ -43,6 +43,7 @@ #include "server/server.h" #include "stats/disk_stats.h" #include "stats/log_collector.h" +#include "status.h" #include "storage/redis_db.h" #include "storage/redis_pubsub.h" #include "storage/scripting.h" @@ -103,24 +104,28 @@ Status ParseTTL(const std::vector &args, std::unordered_map(args[++i], 10); if (!parse_result) { return Status(Status::RedisParseErr, errValueNotInteger); } ttl = *parse_result; if (ttl <= 0) return Status(Status::RedisParseErr, errInvalidExpireTime); - } else if (opt == "exat" && !ttl && !expire && !last_arg) { + } else if (opt == "exat" && !last_arg) { + exat_exist = 1; auto parse_result = ParseInt(args[++i], 10); if (!parse_result) { return Status(Status::RedisParseErr, errValueNotInteger); } expire = *parse_result; if (expire <= 0) return Status(Status::RedisParseErr, errInvalidExpireTime); - } else if (opt == "pxat" && !ttl && !expire && !last_arg) { + } else if (opt == "pxat" && !last_arg) { + pxat_exist = 1; auto parse_result = ParseInt(args[++i], 10); if (!parse_result) { return Status(Status::RedisParseErr, errValueNotInteger); @@ -132,7 +137,8 @@ Status ParseTTL(const std::vector &args, std::unordered_map(expire_ms / 1000); } - } else if (opt == "px" && !ttl && !last_arg) { + } else if (opt == "px" && !last_arg) { + px_exist = 1; int64_t ttl_ms = 0; auto parse_result = ParseInt(args[++i], 10); if (!parse_result) { @@ -154,6 +160,9 @@ Status ParseTTL(const std::vector &args, std::unordered_map= 2) { + return Status(Status::NotOK, errInvalidSyntax); + } if (!ttl && expire) { int64_t now; rocksdb::Env::Default()->GetCurrentTime(&now); @@ -829,10 +838,12 @@ class CommandCAS : public Commander { public: Status Parse(const std::vector &args) override { bool last_arg; + bool ex_exist = false, px_exist = false; for (size_t i = 4; i < args.size(); i++) { last_arg = (i == args.size() - 1); std::string opt = Util::ToLower(args[i]); if (opt == "ex") { + ex_exist = true; if (last_arg) return Status(Status::NotOK, errWrongNumOfArguments); auto parse_result = ParseInt(args_[++i].c_str(), 10); if (!parse_result) { @@ -841,6 +852,7 @@ class CommandCAS : public Commander { ttl_ = *parse_result; if (ttl_ <= 0) return Status(Status::RedisParseErr, errInvalidExpireTime); } else if (opt == "px") { + px_exist = true; if (last_arg) return Status(Status::NotOK, errWrongNumOfArguments); auto parse_result = ParseInt(args[++i].c_str(), 10); if (!parse_result) { @@ -858,6 +870,9 @@ class CommandCAS : public Commander { return Status(Status::NotOK, errInvalidSyntax); } } + if (ex_exist && px_exist) { + return Status(Status::NotOK, errInvalidSyntax); + } return Commander::Parse(args); } diff --git a/tests/gocase/unit/type/strings/strings_test.go b/tests/gocase/unit/type/strings/strings_test.go index 67eca317070..88e0d53239e 100644 --- a/tests/gocase/unit/type/strings/strings_test.go +++ b/tests/gocase/unit/type/strings/strings_test.go @@ -141,6 +141,14 @@ func TestString(t *testing.T) { util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5*time.Second, 10*time.Second) }) + t.Run("GETEX Duplicate EX option", func(t *testing.T) { + require.NoError(t, rdb.Del(ctx, "foo").Err()) + require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err()) + require.NoError(t, rdb.GetEx(ctx, "foo", 10*time.Second).Err()) + require.NoError(t, rdb.Do(ctx, "getex", "foo", "ex", 1, "ex", 10).Err()) + util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5*time.Second, 10*time.Second) + }) + t.Run("GETEX PX option", func(t *testing.T) { require.NoError(t, rdb.Del(ctx, "foo").Err()) require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err()) @@ -148,6 +156,13 @@ func TestString(t *testing.T) { util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5000*time.Millisecond, 10000*time.Millisecond) }) + t.Run("GETEX Duplicate PX option", func(t *testing.T) { + require.NoError(t, rdb.Del(ctx, "foo").Err()) + require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err()) + require.NoError(t, rdb.Do(ctx, "getex", "foo", "px", 1, "px", 10000).Err()) + util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5000*time.Millisecond, 10000*time.Millisecond) + }) + t.Run("GETEX EXAT option", func(t *testing.T) { require.NoError(t, rdb.Del(ctx, "foo").Err()) require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err()) @@ -155,6 +170,13 @@ func TestString(t *testing.T) { util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5*time.Second, 10*time.Second) }) + t.Run("GETEX Duplicate EXAT option", func(t *testing.T) { + require.NoError(t, rdb.Del(ctx, "foo").Err()) + require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err()) + require.NoError(t, rdb.Do(ctx, "getex", "foo", "exat", time.Now().Add(100*time.Second).Unix(), "exat", time.Now().Add(10*time.Second).Unix()).Err()) + util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5*time.Second, 10*time.Second) + }) + t.Run("GETEX PXAT option", func(t *testing.T) { require.NoError(t, rdb.Del(ctx, "foo").Err()) require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err()) @@ -162,12 +184,64 @@ func TestString(t *testing.T) { util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5000*time.Millisecond, 10000*time.Millisecond) }) + t.Run("GETEX Duplicate PXAT option", func(t *testing.T) { + require.NoError(t, rdb.Del(ctx, "foo").Err()) + require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err()) + require.NoError(t, rdb.Do(ctx, "getex", "foo", "pxat", time.Now().Add(1000000*time.Millisecond).UnixMilli(), "pxat", time.Now().Add(10000*time.Millisecond).UnixMilli()).Err()) + util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5000*time.Millisecond, 10000*time.Millisecond) + }) + t.Run("GETEX PERSIST option", func(t *testing.T) { require.NoError(t, rdb.Del(ctx, "foo").Err()) require.NoError(t, rdb.Set(ctx, "foo", "bar", 10*time.Second).Err()) util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5*time.Second, 10*time.Second) require.NoError(t, rdb.Do(ctx, "getex", "foo", "persist").Err()) require.EqualValues(t, -1, rdb.TTL(ctx, "foo").Val()) + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "ex", 10, "persist").Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "px", 10000, "persist").Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "pxat", time.Now().Add(10000*time.Millisecond).UnixMilli(), "persist").Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "exat", time.Now().Add(100*time.Second).Unix(), "persist").Err(), "syntax err") + }) + + t.Run("GETEX with incorrect use of multi options should result in syntax err", func(t *testing.T) { + options := []string{"px", "ex", "pxat", "exat"} + for i := 0; i < (1 << 4); i++ { + cnt := 0 + var op1, op1Value, op2, op2Value string + for j := 0; j < 4; j++ { + if i&(1< Date: Fri, 21 Oct 2022 22:17:40 +0800 Subject: [PATCH 2/4] Use simpler test code --- .../gocase/unit/type/strings/strings_test.go | 88 +++---------------- 1 file changed, 12 insertions(+), 76 deletions(-) diff --git a/tests/gocase/unit/type/strings/strings_test.go b/tests/gocase/unit/type/strings/strings_test.go index 88e0d53239e..e9013afa01a 100644 --- a/tests/gocase/unit/type/strings/strings_test.go +++ b/tests/gocase/unit/type/strings/strings_test.go @@ -204,44 +204,12 @@ func TestString(t *testing.T) { }) t.Run("GETEX with incorrect use of multi options should result in syntax err", func(t *testing.T) { - options := []string{"px", "ex", "pxat", "exat"} - for i := 0; i < (1 << 4); i++ { - cnt := 0 - var op1, op1Value, op2, op2Value string - for j := 0; j < 4; j++ { - if i&(1< Date: Sat, 22 Oct 2022 00:20:08 +0800 Subject: [PATCH 3/4] use second and enhance test --- .../gocase/unit/type/strings/strings_test.go | 56 +++++++++++++------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/tests/gocase/unit/type/strings/strings_test.go b/tests/gocase/unit/type/strings/strings_test.go index 1f2affc8ff2..ebfb6e4f6a1 100644 --- a/tests/gocase/unit/type/strings/strings_test.go +++ b/tests/gocase/unit/type/strings/strings_test.go @@ -128,7 +128,7 @@ func TestString(t *testing.T) { require.NoError(t, rdb.Expire(ctx, "x", time.Second).Err()) // Wait for the key to expire - time.Sleep(2000 * time.Millisecond) + time.Sleep(2 * time.Second) require.NoError(t, rdb.SetNX(ctx, "x", "20", 0).Err()) require.Equal(t, "20", rdb.Get(ctx, "x").Val()) @@ -152,15 +152,15 @@ func TestString(t *testing.T) { t.Run("GETEX PX option", func(t *testing.T) { require.NoError(t, rdb.Del(ctx, "foo").Err()) require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err()) - require.NoError(t, rdb.GetEx(ctx, "foo", 10000*time.Millisecond).Err()) - util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5000*time.Millisecond, 10000*time.Millisecond) + require.NoError(t, rdb.GetEx(ctx, "foo", 10*time.Second).Err()) + util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5*time.Second, 10*time.Second) }) t.Run("GETEX Duplicate PX option", func(t *testing.T) { require.NoError(t, rdb.Del(ctx, "foo").Err()) require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err()) require.NoError(t, rdb.Do(ctx, "getex", "foo", "px", 1, "px", 10000).Err()) - util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5000*time.Millisecond, 10000*time.Millisecond) + util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5*time.Second, 10*time.Second) }) t.Run("GETEX EXAT option", func(t *testing.T) { @@ -180,15 +180,15 @@ func TestString(t *testing.T) { t.Run("GETEX PXAT option", func(t *testing.T) { require.NoError(t, rdb.Del(ctx, "foo").Err()) require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err()) - require.NoError(t, rdb.Do(ctx, "getex", "foo", "pxat", time.Now().Add(10000*time.Millisecond).UnixMilli()).Err()) - util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5000*time.Millisecond, 10000*time.Millisecond) + require.NoError(t, rdb.Do(ctx, "getex", "foo", "pxat", time.Now().Add(10*time.Second).UnixMilli()).Err()) + util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5*time.Second, 10*time.Second) }) t.Run("GETEX Duplicate PXAT option", func(t *testing.T) { require.NoError(t, rdb.Del(ctx, "foo").Err()) require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err()) - require.NoError(t, rdb.Do(ctx, "getex", "foo", "pxat", time.Now().Add(1000000*time.Millisecond).UnixMilli(), "pxat", time.Now().Add(10000*time.Millisecond).UnixMilli()).Err()) - util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5000*time.Millisecond, 10000*time.Millisecond) + require.NoError(t, rdb.Do(ctx, "getex", "foo", "pxat", time.Now().Add(1000*time.Second).UnixMilli(), "pxat", time.Now().Add(10*time.Second).UnixMilli()).Err()) + util.BetweenValues(t, rdb.TTL(ctx, "foo").Val(), 5*time.Second, 10*time.Second) }) t.Run("GETEX PERSIST option", func(t *testing.T) { @@ -199,17 +199,30 @@ func TestString(t *testing.T) { require.EqualValues(t, -1, rdb.TTL(ctx, "foo").Val()) require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "ex", 10, "persist").Err(), "syntax err") require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "px", 10000, "persist").Err(), "syntax err") - require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "pxat", time.Now().Add(10000*time.Millisecond).UnixMilli(), "persist").Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "pxat", time.Now().Add(10*time.Second).UnixMilli(), "persist").Err(), "syntax err") require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "exat", time.Now().Add(100*time.Second).Unix(), "persist").Err(), "syntax err") + + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "persist", "ex", 10).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "persist", "px", 10000).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "persist", "pxat", time.Now().Add(10*time.Second).UnixMilli()).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "persist", "exat", time.Now().Add(100*time.Second).Unix()).Err(), "syntax err") + }) t.Run("GETEX with incorrect use of multi options should result in syntax err", func(t *testing.T) { require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "px", 100, "ex", 10).Err(), "syntax err") - require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "px", 100, "pxat", time.Now().Add(10000*time.Millisecond).UnixMilli()).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "px", 100, "pxat", time.Now().Add(10*time.Second).UnixMilli()).Err(), "syntax err") require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "px", 100, "exat", time.Now().Add(10*time.Second).Unix()).Err(), "syntax err") - require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "ex", 10, "pxat", time.Now().Add(10000*time.Millisecond).UnixMilli()).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "ex", 10, "pxat", time.Now().Add(10*time.Second).UnixMilli()).Err(), "syntax err") require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "ex", 10, "exat", time.Now().Add(10*time.Second).Unix()).Err(), "syntax err") - require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "pxat", time.Now().Add(10000*time.Millisecond).UnixMilli(), "exat", time.Now().Add(10*time.Second).Unix()).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "pxat", time.Now().Add(10*time.Second).UnixMilli(), "exat", time.Now().Add(10*time.Second).Unix()).Err(), "syntax err") + + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "ex", 10, "px", 100).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "pxat", time.Now().Add(10*time.Second).UnixMilli(), "px", 100).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "exat", time.Now().Add(10*time.Second).Unix(), "px", 100).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "pxat", time.Now().Add(10*time.Second).UnixMilli(), "ex", 10).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "exat", time.Now().Add(10*time.Second).Unix(), "ex", 10).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "getex", "foo", "exat", time.Now().Add(10*time.Second).Unix(), "pxat", time.Now().Add(10*time.Second).UnixMilli()).Err(), "syntax err") }) t.Run("GETEX no option", func(t *testing.T) { @@ -574,12 +587,21 @@ func TestString(t *testing.T) { t.Run("Extended SET with incorrect use of multi options should result in syntax err", func(t *testing.T) { require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "px", 100, "ex", 10).Err(), "syntax err") - require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "px", 100, "pxat", time.Now().Add(10000*time.Millisecond).UnixMilli()).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "px", 100, "pxat", time.Now().Add(10*time.Second).UnixMilli()).Err(), "syntax err") require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "px", 100, "exat", time.Now().Add(10*time.Second).Unix()).Err(), "syntax err") - require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "ex", 10, "pxat", time.Now().Add(10000*time.Millisecond).UnixMilli()).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "ex", 10, "pxat", time.Now().Add(10*time.Second).UnixMilli()).Err(), "syntax err") require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "ex", 10, "exat", time.Now().Add(10*time.Second).Unix()).Err(), "syntax err") - require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "pxat", time.Now().Add(10000*time.Millisecond).UnixMilli(), "exat", time.Now().Add(10*time.Second).Unix()).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "pxat", time.Now().Add(10*time.Second).UnixMilli(), "exat", time.Now().Add(10*time.Second).Unix()).Err(), "syntax err") + + require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "ex", 10, "px", 100).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "pxat", time.Now().Add(10*time.Second).UnixMilli(), "px", 100).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "exat", time.Now().Add(10*time.Second).Unix(), "px", 100).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "pxat", time.Now().Add(10*time.Second).UnixMilli(), "ex", 10).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "exat", time.Now().Add(10*time.Second).Unix(), "ex", 10).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "exat", time.Now().Add(10*time.Second).Unix(), "pxat", time.Now().Add(10*time.Second).UnixMilli()).Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "NX", "XX").Err(), "syntax err") + require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "XX", "NX").Err(), "syntax err") }) t.Run("Extended SET with incorrect expire value", func(t *testing.T) { @@ -634,7 +656,7 @@ func TestString(t *testing.T) { require.EqualValues(t, 1, rdb.Do(ctx, "CAS", "cas_key", "123", "234", "ex", "1").Val()) require.Equal(t, "234", rdb.Get(ctx, "cas_key").Val()) - time.Sleep(2000 * time.Millisecond) + time.Sleep(2 * time.Second) require.Equal(t, "", rdb.Get(ctx, "cas_key").Val()) }) @@ -650,7 +672,7 @@ func TestString(t *testing.T) { require.NoError(t, rdb.Del(ctx, "cas_key").Err()) require.NoError(t, rdb.Set(ctx, "cas_key", "123", 0).Err()) require.EqualValues(t, 1, rdb.Do(ctx, "CAS", "cas_key", "123", "234", "px", 1000, "px", 10000).Val()) - util.BetweenValues(t, rdb.TTL(ctx, "cas_key").Val(), 5000*time.Millisecond, 10000*time.Millisecond) + util.BetweenValues(t, rdb.TTL(ctx, "cas_key").Val(), 5*time.Second, 10*time.Second) }) t.Run("CAS expire PX option and EX option exist at the same time", func(t *testing.T) { From 262c361dfad17408c261b8dc0155ba68188db4c4 Mon Sep 17 00:00:00 2001 From: tanruixiang <819464715@qq.com> Date: Sat, 22 Oct 2022 11:21:17 +0800 Subject: [PATCH 4/4] adjust code style --- src/commands/redis_cmd.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/commands/redis_cmd.cc b/src/commands/redis_cmd.cc index f2d8af7cf3c..afd61acb2a3 100644 --- a/src/commands/redis_cmd.cc +++ b/src/commands/redis_cmd.cc @@ -104,12 +104,12 @@ Status ParseTTL(const std::vector &args, std::unordered_map(args[++i], 10); if (!parse_result) { return Status(Status::RedisParseErr, errValueNotInteger); @@ -117,7 +117,7 @@ Status ParseTTL(const std::vector &args, std::unordered_map(args[++i], 10); if (!parse_result) { return Status(Status::RedisParseErr, errValueNotInteger); @@ -125,7 +125,7 @@ Status ParseTTL(const std::vector &args, std::unordered_map(args[++i], 10); if (!parse_result) { return Status(Status::RedisParseErr, errValueNotInteger); @@ -138,7 +138,7 @@ Status ParseTTL(const std::vector &args, std::unordered_map(expire_ms / 1000); } } else if (opt == "px" && !last_arg) { - px_exist = 1; + has_px = 1; int64_t ttl_ms = 0; auto parse_result = ParseInt(args[++i], 10); if (!parse_result) { @@ -160,7 +160,7 @@ Status ParseTTL(const std::vector &args, std::unordered_map= 2) { + if (has_px + has_ex + has_exat + has_pxat > 1) { return Status(Status::NotOK, errInvalidSyntax); } if (!ttl && expire) { @@ -870,7 +870,7 @@ class CommandCAS : public Commander { return Status(Status::NotOK, errInvalidSyntax); } } - if (ex_exist && px_exist) { + if (ex_exist + px_exist > 1) { return Status(Status::NotOK, errInvalidSyntax); } return Commander::Parse(args);