This is an automated email from the ASF dual-hosted git repository.
tison pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/incubator-kvrocks.git
The following commit(s) were added to refs/heads/unstable by this push:
new 30a03486 Implement the ZADD options (#1022)
30a03486 is described below
commit 30a0348636a79034c464e2f8cbda2d10dec49ce8
Author: manchurio <[email protected]>
AuthorDate: Fri Oct 28 13:53:37 2022 +0800
Implement the ZADD options (#1022)
Co-authored-by: Twice <[email protected]>
---
src/commands/redis_cmd.cc | 64 ++++++++++++++++++++++++---
src/types/redis_geo.cc | 4 +-
src/types/redis_zset.cc | 24 ++++++++--
src/types/redis_zset.h | 27 +++++++++++-
tests/cppunit/compact_test.cc | 2 +-
tests/cppunit/disk_test.cc | 2 +-
tests/cppunit/t_zset_test.cc | 30 ++++++-------
tests/gocase/unit/type/zset/zset_test.go | 76 ++++++++++++++++++++++++++++++++
8 files changed, 200 insertions(+), 29 deletions(-)
diff --git a/src/commands/redis_cmd.cc b/src/commands/redis_cmd.cc
index 903ca8fa..1f98e64f 100644
--- a/src/commands/redis_cmd.cc
+++ b/src/commands/redis_cmd.cc
@@ -74,6 +74,7 @@ const char *errUnbalancedStreamList =
"Unbalanced XREAD list of streams: for each stream key an ID or '$' must
be specified.";
const char *errTimeoutIsNegative = "timeout is negative";
const char *errLimitOptionNotAllowed = "syntax error, LIMIT cannot be used
without the special ~ option";
+const char *errZSetLTGTNX = "GT, LT, and/or NX options at the same time are
not compatible";
enum class AuthResult {
OK,
@@ -2377,12 +2378,20 @@ class CommandSInterStore : public Commander {
class CommandZAdd : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
- if (args.size() % 2 != 0) {
- return Status(Status::RedisParseErr, errInvalidSyntax);
+ unsigned index = 2;
+ parseFlags(args, index);
+ if (auto s = validateFlags(); !s.IsOK()) {
+ return s;
+ }
+ if (auto left = (args.size() - index); left >= 0) {
+ if (flags_.HasIncr() && left != 2) {
+ return Status(Status::RedisParseErr, "INCR option supports a single
increment-element pair");
+ } else if (left % 2 != 0 || left == 0) {
+ return Status(Status::RedisParseErr, errInvalidSyntax);
+ }
}
-
try {
- for (unsigned i = 2; i < args.size(); i += 2) {
+ for (unsigned i = index; i < args.size(); i += 2) {
double score = std::stod(args[i]);
if (std::isnan(score)) {
return Status(Status::RedisParseErr, "ERR score is not a valid
float");
@@ -2397,19 +2406,62 @@ class CommandZAdd : public Commander {
Status Execute(Server *svr, Connection *conn, std::string *output) override {
int ret;
+ double old_score = member_scores_[0].score;
Redis::ZSet zset_db(svr->storage_, conn->GetNamespace());
- rocksdb::Status s = zset_db.Add(args_[1], 0, &member_scores_, &ret);
+ rocksdb::Status s = zset_db.Add(args_[1], flags_, &member_scores_, &ret);
if (!s.ok()) {
return Status(Status::RedisExecErr, s.ToString());
}
- *output = Redis::Integer(ret);
+ if (flags_.HasIncr()) {
+ auto new_score = member_scores_[0].score;
+ if ((flags_.HasNX() || flags_.HasXX() || flags_.HasLT() ||
flags_.HasGT()) && old_score == new_score &&
+ ret == 0) { // not the first time using incr && score not changed
+ *output = Redis::NilString();
+ return Status::OK();
+ }
+ *output = Redis::BulkString(Util::Float2String(new_score));
+ } else {
+ *output = Redis::Integer(ret);
+ }
return Status::OK();
}
private:
std::vector<MemberScore> member_scores_;
+ ZAddFlags flags_{0};
+
+ void parseFlags(const std::vector<std::string> &args, unsigned &index);
+ Status validateFlags() const;
};
+void CommandZAdd::parseFlags(const std::vector<std::string> &args, unsigned
&index) {
+ std::unordered_map<std::string, ZSetFlags> options = {{"xx", kZSetXX},
{"nx", kZSetNX}, {"ch", kZSetCH},
+ {"lt", kZSetLT},
{"gt", kZSetGT}, {"incr", kZSetIncr}};
+ for (unsigned i = 2; i < args.size(); i++) {
+ auto option = Util::ToLower(args[i]);
+ auto it = options.find(option);
+ if (it != options.end()) {
+ flags_.SetFlag(it->second);
+ index++;
+ } else {
+ break;
+ }
+ }
+}
+
+Status CommandZAdd::validateFlags() const {
+ if (!flags_.HasAnyFlags()) {
+ return Status::OK();
+ }
+ if (flags_.HasNX() && flags_.HasXX()) {
+ return Status(Status::RedisParseErr, "XX and NX options at the same time
are not compatible");
+ }
+ if ((flags_.HasLT() && flags_.HasGT()) || (flags_.HasLT() && flags_.HasNX())
|| (flags_.HasGT() && flags_.HasNX())) {
+ return Status(Status::RedisParseErr, errZSetLTGTNX);
+ }
+ return Status::OK();
+}
+
class CommandZCount : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
diff --git a/src/types/redis_geo.cc b/src/types/redis_geo.cc
index 293ca377..0400eddc 100644
--- a/src/types/redis_geo.cc
+++ b/src/types/redis_geo.cc
@@ -35,7 +35,7 @@ rocksdb::Status Geo::Add(const Slice &user_key,
std::vector<GeoPoint> *geo_point
GeoHashFix52Bits bits = GeoHashHelper::Align52Bits(hash);
member_scores.emplace_back(MemberScore{geo_point.member,
static_cast<double>(bits)});
}
- return ZSet::Add(user_key, 0, &member_scores, ret);
+ return ZSet::Add(user_key, ZAddFlags::Default(), &member_scores, ret);
}
rocksdb::Status Geo::Dist(const Slice &user_key, const Slice &member_1, const
Slice &member_2, double *dist) {
@@ -120,7 +120,7 @@ rocksdb::Status Geo::Radius(const Slice &user_key, double
longitude, double lati
member_scores.emplace_back(MemberScore{geo_point.member, score});
}
int ret;
- ZSet::Add(store_key, 0, &member_scores, &ret);
+ ZSet::Add(store_key, ZAddFlags::Default(), &member_scores, &ret);
}
}
diff --git a/src/types/redis_zset.cc b/src/types/redis_zset.cc
index 63d2a9a8..558f25ab 100644
--- a/src/types/redis_zset.cc
+++ b/src/types/redis_zset.cc
@@ -37,7 +37,7 @@ rocksdb::Status ZSet::GetMetadata(const Slice &ns_key,
ZSetMetadata *metadata) {
return Database::GetMetadata(kRedisZSet, ns_key, metadata);
}
-rocksdb::Status ZSet::Add(const Slice &user_key, uint8_t flags,
std::vector<MemberScore> *mscores, int *ret) {
+rocksdb::Status ZSet::Add(const Slice &user_key, ZAddFlags flags,
std::vector<MemberScore> *mscores, int *ret) {
*ret = 0;
std::string ns_key;
@@ -49,6 +49,7 @@ rocksdb::Status ZSet::Add(const Slice &user_key, uint8_t
flags, std::vector<Memb
if (!s.ok() && !s.IsNotFound()) return s;
int added = 0;
+ int changed = 0;
rocksdb::WriteBatch batch;
WriteBatchLogData log_data(kRedisZSet);
batch.PutLogData(log_data.Encode());
@@ -76,14 +77,24 @@ rocksdb::Status ZSet::Add(const Slice &user_key, uint8_t
flags, std::vector<Memb
s = db_->Get(rocksdb::ReadOptions(), member_key, &old_score_bytes);
if (!s.ok() && !s.IsNotFound()) return s;
if (s.ok()) {
+ if (!s.IsNotFound() && flags.HasNX()) {
+ continue;
+ }
double old_score = DecodeDouble(old_score_bytes.data());
- if (flags == kZSetIncr) {
+ if (flags.HasIncr()) {
+ if ((flags.HasLT() && (*mscores)[i].score >= 0) || (flags.HasGT() &&
(*mscores)[i].score <= 0)) {
+ continue;
+ }
(*mscores)[i].score += old_score;
if (std::isnan((*mscores)[i].score)) {
return rocksdb::Status::InvalidArgument("resulting score is not a
number (NaN)");
}
}
if ((*mscores)[i].score != old_score) {
+ if ((flags.HasLT() && (*mscores)[i].score >= old_score) ||
+ (flags.HasGT() && (*mscores)[i].score <= old_score)) {
+ continue;
+ }
old_score_bytes.append((*mscores)[i].member);
std::string old_score_key;
InternalKey(ns_key, old_score_bytes, metadata.version,
storage_->IsSlotIdEncoded()).Encode(&old_score_key);
@@ -94,10 +105,14 @@ rocksdb::Status ZSet::Add(const Slice &user_key, uint8_t
flags, std::vector<Memb
new_score_bytes.append((*mscores)[i].member);
InternalKey(ns_key, new_score_bytes, metadata.version,
storage_->IsSlotIdEncoded()).Encode(&new_score_key);
batch.Put(score_cf_handle_, new_score_key, Slice());
+ changed++;
}
continue;
}
}
+ if (flags.HasXX()) {
+ continue;
+ }
std::string score_bytes, score_key;
PutDouble(&score_bytes, (*mscores)[i].score);
batch.Put(member_key, score_bytes);
@@ -113,6 +128,9 @@ rocksdb::Status ZSet::Add(const Slice &user_key, uint8_t
flags, std::vector<Memb
metadata.Encode(&bytes);
batch.Put(metadata_cf_handle_, ns_key, bytes);
}
+ if (flags.HasCH()) {
+ *ret += changed;
+ }
return storage_->Write(storage_->DefaultWriteOptions(), &batch);
}
@@ -138,7 +156,7 @@ rocksdb::Status ZSet::IncrBy(const Slice &user_key, const
Slice &member, double
int ret;
std::vector<MemberScore> mscores;
mscores.emplace_back(MemberScore{member.ToString(), increment});
- rocksdb::Status s = Add(user_key, kZSetIncr, &mscores, &ret);
+ rocksdb::Status s = Add(user_key, ZAddFlags::Incr(), &mscores, &ret);
if (!s.ok()) return s;
*score = mscores[0].score;
return rocksdb::Status::OK();
diff --git a/src/types/redis_zset.h b/src/types/redis_zset.h
index 42b3fd5b..b97e98be 100644
--- a/src/types/redis_zset.h
+++ b/src/types/redis_zset.h
@@ -82,6 +82,31 @@ enum ZSetFlags {
kZSetXX = 1 << 2,
kZSetReversed = 1 << 3,
kZSetRemoved = 1 << 4,
+ kZSetGT = 1 << 5,
+ kZSetLT = 1 << 6,
+ kZSetCH = 1 << 7,
+};
+
+class ZAddFlags {
+ public:
+ explicit ZAddFlags(uint8_t flags = 0) : flags(flags) {}
+
+ bool HasNX() const { return (flags & kZSetNX) != 0; }
+ bool HasXX() const { return (flags & kZSetXX) != 0; }
+ bool HasLT() const { return (flags & kZSetLT) != 0; }
+ bool HasGT() const { return (flags & kZSetGT) != 0; }
+ bool HasCH() const { return (flags & kZSetCH) != 0; }
+ bool HasIncr() const { return (flags & kZSetIncr) != 0; }
+ bool HasAnyFlags() const { return flags != 0; }
+
+ void SetFlag(ZSetFlags setFlags) { flags |= setFlags; }
+
+ static const ZAddFlags Incr() { return ZAddFlags{kZSetIncr}; }
+
+ static const ZAddFlags Default() { return ZAddFlags{0}; }
+
+ private:
+ uint8_t flags = 0;
};
namespace Redis {
@@ -90,7 +115,7 @@ class ZSet : public SubKeyScanner {
public:
explicit ZSet(Engine::Storage *storage, const std::string &ns)
: SubKeyScanner(storage, ns),
score_cf_handle_(storage->GetCFHandle("zset_score")) {}
- rocksdb::Status Add(const Slice &user_key, uint8_t flags,
std::vector<MemberScore> *mscores, int *ret);
+ rocksdb::Status Add(const Slice &user_key, ZAddFlags flags,
std::vector<MemberScore> *mscores, int *ret);
rocksdb::Status Card(const Slice &user_key, int *ret);
rocksdb::Status Count(const Slice &user_key, const ZRangeSpec &spec, int
*ret);
rocksdb::Status IncrBy(const Slice &user_key, const Slice &member, double
increment, double *score);
diff --git a/tests/cppunit/compact_test.cc b/tests/cppunit/compact_test.cc
index 4042e885..1c75e04f 100644
--- a/tests/cppunit/compact_test.cc
+++ b/tests/cppunit/compact_test.cc
@@ -75,7 +75,7 @@ TEST(Compact, Filter) {
auto zset = std::make_unique<Redis::ZSet>(storage_.get(), ns);
std::string expired_zset_key = "expire_zset_key";
std::vector<MemberScore> member_scores = {MemberScore{"z1", 1.1},
MemberScore{"z2", 0.4}};
- zset->Add(expired_zset_key, 0, &member_scores, &ret);
+ zset->Add(expired_zset_key, ZAddFlags::Default(), &member_scores, &ret);
zset->Expire(expired_zset_key, 1); // expired
usleep(10000);
diff --git a/tests/cppunit/disk_test.cc b/tests/cppunit/disk_test.cc
index ca252df4..5472c5b9 100644
--- a/tests/cppunit/disk_test.cc
+++ b/tests/cppunit/disk_test.cc
@@ -148,7 +148,7 @@ TEST_F(RedisDiskTest, ZsetDisk) {
mscores[i].score = 1.0 * value_size[int(values_.size()) - i - 1];
approximate_size += (key_.size() + 8 + mscores[i].member.size() + 8) * 2;
}
- rocksdb::Status s = zset->Add(key_, 0, &mscores, &ret);
+ rocksdb::Status s = zset->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_TRUE(s.ok() && ret == 5);
uint64_t key_size = 0;
EXPECT_TRUE(disk->GetKeySize(key_, kRedisZSet, &key_size).ok());
diff --git a/tests/cppunit/t_zset_test.cc b/tests/cppunit/t_zset_test.cc
index d750ab0a..288fbe11 100644
--- a/tests/cppunit/t_zset_test.cc
+++ b/tests/cppunit/t_zset_test.cc
@@ -47,14 +47,14 @@ TEST_F(RedisZSetTest, Add) {
for (size_t i = 0; i < fields_.size(); i++) {
mscores.emplace_back(MemberScore{fields_[i].ToString(), scores_[i]});
}
- zset->Add(key_, 0, &mscores, &ret);
+ zset->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(static_cast<int>(fields_.size()), ret);
for (size_t i = 0; i < fields_.size(); i++) {
double got;
rocksdb::Status s = zset->Score(key_, fields_[i], &got);
EXPECT_EQ(scores_[i], got);
}
- zset->Add(key_, 0, &mscores, &ret);
+ zset->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(ret, 0);
zset->Del(key_);
}
@@ -65,7 +65,7 @@ TEST_F(RedisZSetTest, IncrBy) {
for (size_t i = 0; i < fields_.size(); i++) {
mscores.emplace_back(MemberScore{fields_[i].ToString(), scores_[i]});
}
- zset->Add(key_, 0, &mscores, &ret);
+ zset->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(fields_.size(), ret);
for (size_t i = 0; i < fields_.size(); i++) {
double increment = 12.3, score;
@@ -81,7 +81,7 @@ TEST_F(RedisZSetTest, Remove) {
for (size_t i = 0; i < fields_.size(); i++) {
mscores.emplace_back(MemberScore{fields_[i].ToString(), scores_[i]});
}
- zset->Add(key_, 0, &mscores, &ret);
+ zset->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(fields_.size(), ret);
zset->Remove(key_, fields_, &ret);
EXPECT_EQ(fields_.size(), ret);
@@ -100,7 +100,7 @@ TEST_F(RedisZSetTest, Range) {
mscores.emplace_back(MemberScore{fields_[i].ToString(), scores_[i]});
}
int count = mscores.size() - 1;
- zset->Add(key_, 0, &mscores, &ret);
+ zset->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(fields_.size(), ret);
zset->Range(key_, 0, -2, 0, &mscores);
EXPECT_EQ(mscores.size(), count);
@@ -118,7 +118,7 @@ TEST_F(RedisZSetTest, RevRange) {
mscores.emplace_back(MemberScore{fields_[i].ToString(), scores_[i]});
}
int count = mscores.size() - 1;
- zset->Add(key_, 0, &mscores, &ret);
+ zset->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(static_cast<int>(fields_.size()), ret);
zset->Range(key_, 0, -2, kZSetReversed, &mscores);
EXPECT_EQ(mscores.size(), count);
@@ -135,7 +135,7 @@ TEST_F(RedisZSetTest, PopMin) {
for (size_t i = 0; i < fields_.size(); i++) {
mscores.emplace_back(MemberScore{fields_[i].ToString(), scores_[i]});
}
- zset->Add(key_, 0, &mscores, &ret);
+ zset->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(static_cast<int>(fields_.size()), ret);
zset->Pop(key_, mscores.size() - 1, true, &mscores);
for (size_t i = 0; i < mscores.size(); i++) {
@@ -154,7 +154,7 @@ TEST_F(RedisZSetTest, PopMax) {
for (size_t i = 0; i < fields_.size(); i++) {
mscores.emplace_back(MemberScore{fields_[i].ToString(), scores_[i]});
}
- zset->Add(key_, 0, &mscores, &ret);
+ zset->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(static_cast<int>(fields_.size()), ret);
zset->Pop(key_, mscores.size() - 1, false, &mscores);
for (size_t i = 0; i < mscores.size(); i++) {
@@ -171,7 +171,7 @@ TEST_F(RedisZSetTest, RangeByLex) {
for (size_t i = 0; i < fields_.size(); i++) {
mscores.emplace_back(MemberScore{fields_[i].ToString(), scores_[i]});
}
- zset->Add(key_, 0, &mscores, &ret);
+ zset->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(fields_.size(), ret);
ZRangeLexSpec spec;
@@ -227,7 +227,7 @@ TEST_F(RedisZSetTest, RangeByScore) {
for (size_t i = 0; i < fields_.size(); i++) {
mscores.emplace_back(MemberScore{fields_[i].ToString(), scores_[i]});
}
- zset->Add(key_, 0, &mscores, &ret);
+ zset->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(fields_.size(), ret);
// test case: inclusive the min and max score
@@ -275,7 +275,7 @@ TEST_F(RedisZSetTest, RangeByScoreWithLimit) {
for (size_t i = 0; i < fields_.size(); i++) {
mscores.emplace_back(MemberScore{fields_[i].ToString(), scores_[i]});
}
- zset->Add(key_, 0, &mscores, &ret);
+ zset->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(fields_.size(), ret);
ZRangeSpec spec;
@@ -296,7 +296,7 @@ TEST_F(RedisZSetTest, RemRangeByScore) {
for (size_t i = 0; i < fields_.size(); i++) {
mscores.emplace_back(MemberScore{fields_[i].ToString(), scores_[i]});
}
- zset->Add(key_, 0, &mscores, &ret);
+ zset->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(fields_.size(), ret);
ZRangeSpec spec;
spec.min = scores_[0];
@@ -315,7 +315,7 @@ TEST_F(RedisZSetTest, RemoveRangeByRank) {
for (size_t i = 0; i < fields_.size(); i++) {
mscores.emplace_back(MemberScore{fields_[i].ToString(), scores_[i]});
}
- zset->Add(key_, 0, &mscores, &ret);
+ zset->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(fields_.size(), ret);
zset->RemoveRangeByRank(key_, 0, fields_.size() - 2, &ret);
EXPECT_EQ(fields_.size() - 1, ret);
@@ -329,7 +329,7 @@ TEST_F(RedisZSetTest, RemoveRevRangeByRank) {
for (size_t i = 0; i < fields_.size(); i++) {
mscores.emplace_back(MemberScore{fields_[i].ToString(), scores_[i]});
}
- zset->Add(key_, 0, &mscores, &ret);
+ zset->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(fields_.size(), ret);
zset->RemoveRangeByRank(key_, 0, fields_.size() - 2, &ret);
EXPECT_EQ(static_cast<int>(fields_.size() - 1), ret);
@@ -343,7 +343,7 @@ TEST_F(RedisZSetTest, Rank) {
for (size_t i = 0; i < fields_.size(); i++) {
mscores.emplace_back(MemberScore{fields_[i].ToString(), scores_[i]});
}
- zset->Add(key_, 0, &mscores, &ret);
+ zset->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(static_cast<int>(fields_.size()), ret);
for (size_t i = 0; i < fields_.size(); i++) {
diff --git a/tests/gocase/unit/type/zset/zset_test.go
b/tests/gocase/unit/type/zset/zset_test.go
index 616387e1..7dae9e62 100644
--- a/tests/gocase/unit/type/zset/zset_test.go
+++ b/tests/gocase/unit/type/zset/zset_test.go
@@ -93,6 +93,82 @@ func basicTests(t *testing.T, rdb *redis.Client, ctx
context.Context, encoding s
require.Equal(t, float64(30), rdb.ZScore(ctx, "ztmp",
"x").Val())
})
+ t.Run(fmt.Sprintf("ZSET ZADD INCR option supports a single pair - %s",
encoding), func(t *testing.T) {
+ rdb.Del(ctx, "ztmp")
+ require.Equal(t, 1.5, rdb.ZAddArgsIncr(ctx, "ztmp",
redis.ZAddArgs{Members: []redis.Z{{Member: "abc", Score: 1.5}}}).Val())
+ require.Contains(t, rdb.ZAddArgsIncr(ctx, "ztmp",
redis.ZAddArgs{Members: []redis.Z{{Member: "abc", Score: 1.5}, {Member:
"adc"}}}).Err(),
+ "INCR option supports a single increment-element pair")
+ })
+
+ t.Run(fmt.Sprintf("ZSET ZADD IncrMixedOtherOptions - %s", encoding),
func(t *testing.T) {
+ rdb.Del(ctx, "ztmp")
+ require.Equal(t, "1.5", rdb.Do(ctx, "zadd", "ztmp", "nx", "nx",
"nx", "nx", "incr", "1.5", "abc").Val())
+ require.Equal(t, redis.Nil, rdb.Do(ctx, "zadd", "ztmp", "nx",
"nx", "nx", "nx", "incr", "1.5", "abc").Err())
+ require.Equal(t, "3", rdb.Do(ctx, "zadd", "ztmp", "xx", "xx",
"xx", "xx", "incr", "1.5", "abc").Val())
+
+ rdb.Del(ctx, "ztmp")
+ require.Equal(t, 1.5, rdb.ZAddArgsIncr(ctx, "ztmp",
redis.ZAddArgs{NX: true, Members: []redis.Z{{Member: "abc", Score:
1.5}}}).Val())
+ require.Equal(t, redis.Nil, rdb.ZAddArgsIncr(ctx, "ztmp",
redis.ZAddArgs{NX: true, Members: []redis.Z{{Member: "abc", Score:
1.5}}}).Err())
+
+ rdb.Del(ctx, "ztmp")
+ require.Equal(t, redis.Nil, rdb.ZAddArgsIncr(ctx, "ztmp",
redis.ZAddArgs{XX: true, Members: []redis.Z{{Member: "abc", Score:
1.5}}}).Err())
+ require.Equal(t, 1.5, rdb.ZAddArgsIncr(ctx, "ztmp",
redis.ZAddArgs{NX: true, Members: []redis.Z{{Member: "abc", Score:
1.5}}}).Val())
+
+ rdb.Del(ctx, "ztmp")
+ require.Equal(t, 1.5, rdb.ZAddArgsIncr(ctx, "ztmp",
redis.ZAddArgs{NX: true, Members: []redis.Z{{Member: "abc", Score:
1.5}}}).Val())
+ require.Equal(t, 3.0, rdb.ZAddArgsIncr(ctx, "ztmp",
redis.ZAddArgs{GT: true, Members: []redis.Z{{Member: "abc", Score:
1.5}}}).Val())
+ require.Equal(t, 0.0, rdb.ZAddArgsIncr(ctx, "ztmp",
redis.ZAddArgs{GT: true, Members: []redis.Z{{Member: "abc", Score:
-1.5}}}).Val())
+ require.Equal(t, redis.Nil, rdb.ZAddArgsIncr(ctx, "ztmp",
redis.ZAddArgs{GT: true, Members: []redis.Z{{Member: "abc", Score:
-1.5}}}).Err())
+
+ rdb.Del(ctx, "ztmp")
+ require.Equal(t, 1.5, rdb.ZAddArgsIncr(ctx, "ztmp",
redis.ZAddArgs{NX: true, Members: []redis.Z{{Member: "abc", Score:
1.5}}}).Val())
+ require.Equal(t, 0.0, rdb.ZAddArgsIncr(ctx, "ztmp",
redis.ZAddArgs{LT: true, Members: []redis.Z{{Member: "abc", Score:
-1.5}}}).Val())
+ require.Equal(t, 0.0, rdb.ZAddArgsIncr(ctx, "ztmp",
redis.ZAddArgs{LT: true, Members: []redis.Z{{Member: "abc", Score:
1.5}}}).Val())
+ require.Equal(t, redis.Nil, rdb.ZAddArgsIncr(ctx, "ztmp",
redis.ZAddArgs{LT: true, Members: []redis.Z{{Member: "abc", Score:
1.5}}}).Err())
+ })
+
+ t.Run(fmt.Sprintf("ZSET ZADD LT/GT with other options - %s", encoding),
func(t *testing.T) {
+ rdb.Del(ctx, "ztmp")
+ require.EqualValues(t, 1, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{Members: []redis.Z{{Member: "abc", Score: 1.5}}}).Val())
+ require.EqualValues(t, 1, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{GT: true, Ch: true, Members: []redis.Z{{Member: "abc", Score:
2.5}}}).Val())
+ require.EqualValues(t, 0, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{GT: true, Ch: true, Members: []redis.Z{{Member: "abc", Score:
2.5}}}).Val())
+ require.EqualValues(t, 0, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{GT: true, Ch: false, Members: []redis.Z{{Member: "abc", Score:
2.5}}}).Val())
+ require.EqualValues(t, 0, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{GT: true, Ch: false, Members: []redis.Z{{Member: "abc", Score:
100}}}).Val())
+ require.Contains(t, rdb.Do(ctx, "zadd", "ztmp", "lt", "gt",
"1", "m1", "2", "m2").Err(),
+ "GT, LT, and/or NX options at the same time are not
compatible")
+
+ rdb.Del(ctx, "ztmp")
+ require.EqualValues(t, 1, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{LT: true, Ch: true, Members: []redis.Z{{Member: "abc", Score:
1.5}}}).Val())
+ require.EqualValues(t, 1, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{LT: true, Ch: true, Members: []redis.Z{{Member: "abc", Score:
1.2}}}).Val())
+ require.EqualValues(t, 0, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{LT: true, Ch: false, Members: []redis.Z{{Member: "abc", Score:
0.5}}}).Val())
+
+ rdb.Del(ctx, "newAbc1", "newAbc2")
+ require.EqualValues(t, 2, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{Ch: true, Members: []redis.Z{{Member: "abc", Score: 0.5},
{Member: "newAbc1", Score: 10}, {Member: "newAbc2"}}}).Val())
+ })
+
+ t.Run(fmt.Sprintf("ZSET ZADD NX/XX option supports a single pair - %s",
encoding), func(t *testing.T) {
+ rdb.Del(ctx, "ztmp")
+ require.EqualValues(t, 2, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{NX: true, Members: []redis.Z{{Member: "a", Score: 1}, {Member:
"b", Score: 2}}}).Val())
+ require.EqualValues(t, 1, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{NX: true, Members: []redis.Z{{Member: "c", Score: 3}}}).Val())
+
+ rdb.Del(ctx, "ztmp")
+ require.EqualValues(t, 1, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{NX: true, Members: []redis.Z{{Member: "abc", Score:
1.5}}}).Val())
+ require.EqualValues(t, 0, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{NX: true, Members: []redis.Z{{Member: "abc", Score:
1.5}}}).Val())
+ require.EqualValues(t, 1, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{XX: true, Ch: true, Members: []redis.Z{{Member: "abc", Score:
2.5}}}).Val())
+ require.EqualValues(t, 0, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{XX: true, Ch: true, Members: []redis.Z{{Member: "abc", Score:
2.5}}}).Val())
+ require.Contains(t, rdb.Do(ctx, "zadd", "ztmp", "nx", "xx",
"1", "m1", "2", "m2").Err(),
+ "XX and NX options at the same time are not compatible")
+
+ require.Contains(t, rdb.Do(ctx, "zadd", "ztmp", "lt", "nx",
"1", "m1", "2", "m2").Err(),
+ "GT, LT, and/or NX options at the same time are not
compatible")
+ require.Contains(t, rdb.Do(ctx, "zadd", "ztmp", "gt", "nx",
"1", "m1", "2", "m2").Err(),
+ "GT, LT, and/or NX options at the same time are not
compatible")
+
+ rdb.Del(ctx, "ztmp")
+ require.EqualValues(t, 1, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{NX: true, Ch: true, Members: []redis.Z{{Member: "abc", Score:
1.5}}}).Val())
+ require.EqualValues(t, 0, rdb.ZAddArgs(ctx, "ztmp",
redis.ZAddArgs{NX: true, Members: []redis.Z{{Member: "abc", Score:
1.5}}}).Val())
+ })
+
t.Run(fmt.Sprintf("ZSET element can't be set to NaN with ZADD - %s",
encoding), func(t *testing.T) {
require.Contains(t, rdb.ZAdd(ctx, "myzset", redis.Z{Score:
math.NaN(), Member: "abc"}).Err(), "float")
})