mirror of
https://github.com/dragonflydb/dragonfly.git
synced 2025-05-11 10:25:47 +02:00
feat(server): SORT command prototype (#311)
feat(server): Implement SORT Fixes #287 Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io> Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io> Co-authored-by: Vladislav Oleshko <vlad@dragonflydb.io>
This commit is contained in:
parent
082ac36ac1
commit
0377481e28
15 changed files with 514 additions and 140 deletions
|
@ -6,6 +6,10 @@ String sizes are limited to 256MB.
|
||||||
Indices (say in GETRANGE and SETRANGE commands) should be signed 32 bit integers in range
|
Indices (say in GETRANGE and SETRANGE commands) should be signed 32 bit integers in range
|
||||||
[-2147483647, 2147483648].
|
[-2147483647, 2147483648].
|
||||||
|
|
||||||
|
### String handling.
|
||||||
|
|
||||||
|
SORT does not take any locale into account.
|
||||||
|
|
||||||
## Expiry ranges.
|
## Expiry ranges.
|
||||||
Expirations are limited to 4 years. For commands with millisecond precision like PEXPIRE or PSETEX,
|
Expirations are limited to 4 years. For commands with millisecond precision like PEXPIRE or PSETEX,
|
||||||
expirations greater than 2^27ms are quietly rounded to the nearest second loosing precision of less than 0.001%.
|
expirations greater than 2^27ms are quietly rounded to the nearest second loosing precision of less than 0.001%.
|
||||||
|
|
|
@ -262,6 +262,16 @@ size_t RobjWrapper::Size() const {
|
||||||
case OBJ_STRING:
|
case OBJ_STRING:
|
||||||
DCHECK_EQ(OBJ_ENCODING_RAW, encoding_);
|
DCHECK_EQ(OBJ_ENCODING_RAW, encoding_);
|
||||||
return sz_;
|
return sz_;
|
||||||
|
case OBJ_LIST:
|
||||||
|
return quicklistCount((quicklist*)inner_obj_);
|
||||||
|
case OBJ_ZSET: {
|
||||||
|
robj self{.type = type_,
|
||||||
|
.encoding = encoding_,
|
||||||
|
.lru = 0,
|
||||||
|
.refcount = OBJ_STATIC_REFCOUNT,
|
||||||
|
.ptr = inner_obj_};
|
||||||
|
return zsetLength(&self);
|
||||||
|
}
|
||||||
case OBJ_SET:
|
case OBJ_SET:
|
||||||
switch (encoding_) {
|
switch (encoding_) {
|
||||||
case kEncodingIntSet: {
|
case kEncodingIntSet: {
|
||||||
|
@ -278,7 +288,7 @@ size_t RobjWrapper::Size() const {
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
LOG(FATAL) << "Unexpected encoding " << encoding_;
|
LOG(FATAL) << "Unexpected encoding " << encoding_;
|
||||||
}
|
};
|
||||||
default:;
|
default:;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -299,6 +299,10 @@ void RedisReplyBuilder::SendNullArray() {
|
||||||
SendRaw("*-1\r\n");
|
SendRaw("*-1\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RedisReplyBuilder::SendEmptyArray() {
|
||||||
|
StartArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
void RedisReplyBuilder::SendStringArr(absl::Span<const std::string_view> arr) {
|
void RedisReplyBuilder::SendStringArr(absl::Span<const std::string_view> arr) {
|
||||||
if (arr.empty()) {
|
if (arr.empty()) {
|
||||||
SendRaw("*0\r\n");
|
SendRaw("*0\r\n");
|
||||||
|
|
|
@ -128,7 +128,10 @@ class RedisReplyBuilder : public SinkReplyBuilder {
|
||||||
void SendError(OpStatus status);
|
void SendError(OpStatus status);
|
||||||
|
|
||||||
virtual void SendSimpleStrArr(const std::string_view* arr, uint32_t count);
|
virtual void SendSimpleStrArr(const std::string_view* arr, uint32_t count);
|
||||||
|
// Send *-1
|
||||||
virtual void SendNullArray();
|
virtual void SendNullArray();
|
||||||
|
// Send *0
|
||||||
|
virtual void SendEmptyArray();
|
||||||
|
|
||||||
virtual void SendStringArr(absl::Span<const std::string_view> arr);
|
virtual void SendStringArr(absl::Span<const std::string_view> arr);
|
||||||
virtual void SendStringArr(absl::Span<const std::string> arr);
|
virtual void SendStringArr(absl::Span<const std::string> arr);
|
||||||
|
|
|
@ -12,7 +12,7 @@ add_library(dragonfly_lib channel_slice.cc command_registry.cc
|
||||||
list_family.cc main_service.cc rdb_load.cc rdb_save.cc replica.cc
|
list_family.cc main_service.cc rdb_load.cc rdb_save.cc replica.cc
|
||||||
snapshot.cc script_mgr.cc server_family.cc malloc_stats.cc
|
snapshot.cc script_mgr.cc server_family.cc malloc_stats.cc
|
||||||
set_family.cc stream_family.cc string_family.cc
|
set_family.cc stream_family.cc string_family.cc
|
||||||
zset_family.cc version.cc bitops_family.cc)
|
zset_family.cc version.cc bitops_family.cc container_utils.cc)
|
||||||
|
|
||||||
cxx_link(dragonfly_lib dfly_transaction dfly_facade redis_lib strings_lib html_lib
|
cxx_link(dragonfly_lib dfly_transaction dfly_facade redis_lib strings_lib html_lib
|
||||||
absl::random_random TRDP::jsoncons)
|
absl::random_random TRDP::jsoncons)
|
||||||
|
|
161
src/server/container_utils.cc
Normal file
161
src/server/container_utils.cc
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
// Copyright 2022, DragonflyDB authors. All rights reserved.
|
||||||
|
// See LICENSE for licensing terms.
|
||||||
|
//
|
||||||
|
#include "server/container_utils.h"
|
||||||
|
|
||||||
|
#include "base/logging.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "redis/intset.h"
|
||||||
|
#include "redis/listpack.h"
|
||||||
|
#include "redis/object.h"
|
||||||
|
#include "redis/redis_aux.h"
|
||||||
|
#include "redis/util.h"
|
||||||
|
#include "redis/zset.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace dfly::container_utils {
|
||||||
|
|
||||||
|
quicklistEntry QLEntry() {
|
||||||
|
quicklistEntry res{.quicklist = NULL,
|
||||||
|
.node = NULL,
|
||||||
|
.zi = NULL,
|
||||||
|
.value = NULL,
|
||||||
|
.longval = 0,
|
||||||
|
.sz = 0,
|
||||||
|
.offset = 0};
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IterateList(const PrimeValue& pv, const IterateFunc& func, long start, long end) {
|
||||||
|
quicklist* ql = static_cast<quicklist*>(pv.RObjPtr());
|
||||||
|
long llen = quicklistCount(ql);
|
||||||
|
if (end < 0 || end >= llen)
|
||||||
|
end = llen - 1;
|
||||||
|
|
||||||
|
quicklistIter* qiter = quicklistGetIteratorAtIdx(ql, AL_START_HEAD, start);
|
||||||
|
quicklistEntry entry = QLEntry();
|
||||||
|
long lrange = end - start + 1;
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
while (success && quicklistNext(qiter, &entry) && lrange-- > 0) {
|
||||||
|
if (entry.value) {
|
||||||
|
success = func(ContainerEntry{reinterpret_cast<char*>(entry.value), entry.sz});
|
||||||
|
} else {
|
||||||
|
success = func(ContainerEntry{nullptr, .longval=entry.longval});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
quicklistReleaseIterator(qiter);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IterateSet(const PrimeValue& pv, const IterateFunc& func) {
|
||||||
|
bool success = true;
|
||||||
|
if (pv.Encoding() == kEncodingIntSet) {
|
||||||
|
intset* is = static_cast<intset*>(pv.RObjPtr());
|
||||||
|
int64_t ival;
|
||||||
|
int ii = 0;
|
||||||
|
|
||||||
|
while (success && intsetGet(is, ii++, &ival)) {
|
||||||
|
success = func(ContainerEntry{nullptr, .longval=ival});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (pv.Encoding() == kEncodingStrMap2) {
|
||||||
|
for (sds ptr : *static_cast<StringSet*>(pv.RObjPtr())) {
|
||||||
|
if (!func(ContainerEntry{ptr, sdslen(ptr)})) {
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dict* ds = static_cast<dict*>(pv.RObjPtr());
|
||||||
|
dictIterator* di = dictGetIterator(ds);
|
||||||
|
dictEntry* de = nullptr;
|
||||||
|
while (success && (de = dictNext(di))) {
|
||||||
|
sds ptr = static_cast<sds>(de->key);
|
||||||
|
success = func(ContainerEntry{ptr, sdslen(ptr)});
|
||||||
|
}
|
||||||
|
dictReleaseIterator(di);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IterateSortedSet(robj* zobj, const IterateSortedFunc& func, int32_t start, int32_t end,
|
||||||
|
bool reverse, bool use_score) {
|
||||||
|
unsigned long llen = zsetLength(zobj);
|
||||||
|
if (end < 0 || unsigned(end) >= llen)
|
||||||
|
end = llen - 1;
|
||||||
|
|
||||||
|
unsigned rangelen = unsigned(end - start) + 1;
|
||||||
|
|
||||||
|
if (zobj->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
|
uint8_t* zl = static_cast<uint8_t*>(zobj->ptr);
|
||||||
|
uint8_t *eptr, *sptr;
|
||||||
|
uint8_t* vstr;
|
||||||
|
unsigned int vlen;
|
||||||
|
long long vlong;
|
||||||
|
double score = 0.0;
|
||||||
|
|
||||||
|
if (reverse) {
|
||||||
|
eptr = lpSeek(zl, -2 - long(2 * start));
|
||||||
|
} else {
|
||||||
|
eptr = lpSeek(zl, 2 * start);
|
||||||
|
}
|
||||||
|
DCHECK(eptr);
|
||||||
|
|
||||||
|
sptr = lpNext(zl, eptr);
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
while (success && rangelen--) {
|
||||||
|
DCHECK(eptr != NULL && sptr != NULL);
|
||||||
|
vstr = lpGetValue(eptr, &vlen, &vlong);
|
||||||
|
|
||||||
|
// don't bother to extract the score if it's gonna be ignored.
|
||||||
|
if (use_score)
|
||||||
|
score = zzlGetScore(sptr);
|
||||||
|
|
||||||
|
if (vstr == NULL) {
|
||||||
|
success = func(ContainerEntry{nullptr, .longval=vlong}, score);
|
||||||
|
} else {
|
||||||
|
success = func(ContainerEntry{reinterpret_cast<const char*>(vstr), vlen}, score);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reverse) {
|
||||||
|
zzlPrev(zl, &eptr, &sptr);
|
||||||
|
} else {
|
||||||
|
zzlNext(zl, &eptr, &sptr);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
} else {
|
||||||
|
CHECK_EQ(zobj->encoding, OBJ_ENCODING_SKIPLIST);
|
||||||
|
zset* zs = static_cast<zset*>(zobj->ptr);
|
||||||
|
zskiplist* zsl = zs->zsl;
|
||||||
|
zskiplistNode* ln;
|
||||||
|
|
||||||
|
/* Check if starting point is trivial, before doing log(N) lookup. */
|
||||||
|
if (reverse) {
|
||||||
|
ln = zsl->tail;
|
||||||
|
unsigned long llen = zsetLength(zobj);
|
||||||
|
if (start > 0)
|
||||||
|
ln = zslGetElementByRank(zsl, llen - start);
|
||||||
|
} else {
|
||||||
|
ln = zsl->header->level[0].forward;
|
||||||
|
if (start > 0)
|
||||||
|
ln = zslGetElementByRank(zsl, start + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
while (success && rangelen--) {
|
||||||
|
DCHECK(ln != NULL);
|
||||||
|
success = func(ContainerEntry{ln->ele, sdslen(ln->ele)}, ln->score);
|
||||||
|
ln = reverse ? ln->backward : ln->level[0].forward;
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dfly::container_utils
|
69
src/server/container_utils.h
Normal file
69
src/server/container_utils.h
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright 2022, DragonflyDB authors. All rights reserved.
|
||||||
|
// See LICENSE for licensing terms.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/compact_object.h"
|
||||||
|
#include "core/string_set.h"
|
||||||
|
#include "server/common.h"
|
||||||
|
#include "server/table.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "redis/object.h"
|
||||||
|
#include "redis/quicklist.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace dfly {
|
||||||
|
|
||||||
|
namespace container_utils {
|
||||||
|
|
||||||
|
// IsContainer returns true if the iterator points to a container type.
|
||||||
|
inline bool IsContainer(const PrimeValue& pv) {
|
||||||
|
unsigned type = pv.ObjType();
|
||||||
|
return (type == OBJ_LIST || type == OBJ_SET || type == OBJ_ZSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create empty quicklistEntry
|
||||||
|
quicklistEntry QLEntry();
|
||||||
|
|
||||||
|
// Stores either:
|
||||||
|
// - A single long long value (longval) when value = nullptr
|
||||||
|
// - A single char* (value) when value != nullptr
|
||||||
|
struct ContainerEntry {
|
||||||
|
const char* value;
|
||||||
|
union {
|
||||||
|
size_t length;
|
||||||
|
long long longval;
|
||||||
|
};
|
||||||
|
std::string ToString() {
|
||||||
|
if (value)
|
||||||
|
return {value, length};
|
||||||
|
else
|
||||||
|
return absl::StrCat(longval);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using IterateFunc = std::function<bool(ContainerEntry)>;
|
||||||
|
using IterateSortedFunc = std::function<bool(ContainerEntry, double)>;
|
||||||
|
|
||||||
|
// Iterate over all values and call func(val). Iteration stops as soon
|
||||||
|
// as func return false. Returns true if it successfully processed all elements
|
||||||
|
// without stopping.
|
||||||
|
bool IterateList(const PrimeValue& pv, const IterateFunc& func, long start = 0, long end = -1);
|
||||||
|
|
||||||
|
// Iterate over all values and call func(val). Iteration stops as soon
|
||||||
|
// as func return false. Returns true if it successfully processed all elements
|
||||||
|
// without stopping.
|
||||||
|
bool IterateSet(const PrimeValue& pv, const IterateFunc& func);
|
||||||
|
|
||||||
|
// Iterate over all values and call func(val). Iteration stops as soon
|
||||||
|
// as func return false. Returns true if it successfully processed all elements
|
||||||
|
// without stopping.
|
||||||
|
bool IterateSortedSet(robj* zobj, const IterateSortedFunc& func, int32_t start = 0, int32_t end = -1,
|
||||||
|
bool reverse = false, bool use_score = false);
|
||||||
|
|
||||||
|
}; // namespace container_utils
|
||||||
|
|
||||||
|
} // namespace dfly
|
|
@ -14,6 +14,7 @@ extern "C" {
|
||||||
#include "server/blocking_controller.h"
|
#include "server/blocking_controller.h"
|
||||||
#include "server/command_registry.h"
|
#include "server/command_registry.h"
|
||||||
#include "server/conn_context.h"
|
#include "server/conn_context.h"
|
||||||
|
#include "server/container_utils.h"
|
||||||
#include "server/engine_shard_set.h"
|
#include "server/engine_shard_set.h"
|
||||||
#include "server/error.h"
|
#include "server/error.h"
|
||||||
#include "server/transaction.h"
|
#include "server/transaction.h"
|
||||||
|
@ -208,8 +209,7 @@ bool ScanCb(const OpArgs& op_args, PrimeIterator it, const ScanOpts& opts, Strin
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpScan(const OpArgs& op_args, const ScanOpts& scan_opts, uint64_t* cursor,
|
void OpScan(const OpArgs& op_args, const ScanOpts& scan_opts, uint64_t* cursor, StringVec* vec) {
|
||||||
StringVec* vec) {
|
|
||||||
auto& db_slice = op_args.shard->db_slice();
|
auto& db_slice = op_args.shard->db_slice();
|
||||||
DCHECK(db_slice.IsDbValid(op_args.db_ind));
|
DCHECK(db_slice.IsDbValid(op_args.db_ind));
|
||||||
|
|
||||||
|
@ -463,6 +463,170 @@ void GenericFamily::Stick(CmdArgList args, ConnectionContext* cntx) {
|
||||||
(*cntx)->SendLong(match_cnt);
|
(*cntx)->SendLong(match_cnt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used to conditionally store double score
|
||||||
|
struct SortEntryScore {
|
||||||
|
double score;
|
||||||
|
};
|
||||||
|
|
||||||
|
// SortEntry stores all data required for sorting
|
||||||
|
template <bool ALPHA>
|
||||||
|
struct SortEntry
|
||||||
|
// Store score only if we need it
|
||||||
|
: public std::conditional_t<ALPHA, std::tuple<>, SortEntryScore> {
|
||||||
|
std::string key;
|
||||||
|
|
||||||
|
bool Parse(std::string&& item) {
|
||||||
|
if constexpr (!ALPHA) {
|
||||||
|
if (!absl::SimpleAtod(item, &this->score))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
key = std::move(item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Parse(int64_t item) {
|
||||||
|
if constexpr (!ALPHA) {
|
||||||
|
this->score = item;
|
||||||
|
}
|
||||||
|
key = absl::StrCat(item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::conditional_t<ALPHA, const std::string&, double> Cmp() const {
|
||||||
|
if constexpr (ALPHA) {
|
||||||
|
return key;
|
||||||
|
} else {
|
||||||
|
return this->score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// std::variant of all possible vectors of SortEntries
|
||||||
|
using SortEntryList = std::variant<
|
||||||
|
// Used when sorting by double values
|
||||||
|
std::vector<SortEntry<false>>,
|
||||||
|
// Used when sorting by string values
|
||||||
|
std::vector<SortEntry<true>>>;
|
||||||
|
|
||||||
|
// Create SortEntryList based on runtime arguments
|
||||||
|
SortEntryList MakeSortEntryList(bool alpha) {
|
||||||
|
if (alpha)
|
||||||
|
return SortEntryList{std::vector<SortEntry<true>>{}};
|
||||||
|
else
|
||||||
|
return SortEntryList{std::vector<SortEntry<false>>{}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over container with generic function that accepts strings and ints
|
||||||
|
template <typename F> bool Iterate(const PrimeValue& pv, F&& func) {
|
||||||
|
auto cb = [&func](container_utils::ContainerEntry ce) {
|
||||||
|
if (ce.value)
|
||||||
|
return func(ce.ToString());
|
||||||
|
else
|
||||||
|
return func(ce.longval);
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (pv.ObjType()) {
|
||||||
|
case OBJ_LIST:
|
||||||
|
return container_utils::IterateList(pv, cb);
|
||||||
|
case OBJ_SET:
|
||||||
|
return container_utils::IterateSet(pv, cb);
|
||||||
|
case OBJ_ZSET:
|
||||||
|
return container_utils::IterateSortedSet(
|
||||||
|
pv.AsRObj(), [&cb](container_utils::ContainerEntry ce, double) { return cb(ce); });
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a SortEntryList from given key
|
||||||
|
OpResult<SortEntryList> OpFetchSortEntries(const OpArgs& op_args, std::string_view key,
|
||||||
|
bool alpha) {
|
||||||
|
using namespace container_utils;
|
||||||
|
|
||||||
|
auto [it, _] = op_args.shard->db_slice().FindExt(op_args.db_ind, key);
|
||||||
|
if (!IsValid(it) || !IsContainer(it->second)) {
|
||||||
|
return OpStatus::KEY_NOTFOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = MakeSortEntryList(alpha);
|
||||||
|
bool success = std::visit(
|
||||||
|
[&pv = it->second](auto& entries) {
|
||||||
|
entries.reserve(pv.Size());
|
||||||
|
return Iterate(pv, [&entries](auto&& val) {
|
||||||
|
return entries.emplace_back().Parse(std::forward<decltype(val)>(val));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
result);
|
||||||
|
return success ? OpResult{std::move(result)} : OpStatus::WRONG_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericFamily::Sort(CmdArgList args, ConnectionContext* cntx) {
|
||||||
|
std::string_view key = ArgS(args, 1);
|
||||||
|
bool alpha = false;
|
||||||
|
bool reversed = false;
|
||||||
|
std::optional<std::pair<size_t, size_t>> bounds;
|
||||||
|
|
||||||
|
for (size_t i = 2; i < args.size(); i++) {
|
||||||
|
ToUpper(&args[i]);
|
||||||
|
|
||||||
|
std::string_view arg = ArgS(args, i);
|
||||||
|
if (arg == "ALPHA") {
|
||||||
|
alpha = true;
|
||||||
|
} else if (arg == "DESC") {
|
||||||
|
reversed = true;
|
||||||
|
} else if (arg == "LIMIT") {
|
||||||
|
int offset, limit;
|
||||||
|
if (i + 2 >= args.size()) {
|
||||||
|
return (*cntx)->SendError(kSyntaxErr);
|
||||||
|
}
|
||||||
|
if (!absl::SimpleAtoi(ArgS(args, i + 1), &offset) ||
|
||||||
|
!absl::SimpleAtoi(ArgS(args, i + 2), &limit)) {
|
||||||
|
return (*cntx)->SendError(kInvalidIntErr);
|
||||||
|
}
|
||||||
|
bounds = {offset, limit};
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpResult<SortEntryList> entries =
|
||||||
|
cntx->transaction->ScheduleSingleHopT([&](Transaction* t, EngineShard* shard) {
|
||||||
|
return OpFetchSortEntries(t->GetOpArgs(shard), key, alpha);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (entries.status() == OpStatus::WRONG_TYPE)
|
||||||
|
return (*cntx)->SendError("One or more scores can't be converted into double");
|
||||||
|
|
||||||
|
if (!entries.ok())
|
||||||
|
return (*cntx)->SendEmptyArray();
|
||||||
|
|
||||||
|
auto sort_call = [cntx, bounds, reversed, key](auto& entries) {
|
||||||
|
if (bounds) {
|
||||||
|
auto sort_it = entries.begin() + std::min(bounds->first + bounds->second, entries.size());
|
||||||
|
std::partial_sort(entries.begin(), sort_it, entries.end(),
|
||||||
|
[reversed](const auto& lhs, const auto& rhs) {
|
||||||
|
return bool(lhs.Cmp() < rhs.Cmp()) ^ reversed;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
std::sort(entries.begin(), entries.end(), [reversed](const auto& lhs, const auto& rhs) {
|
||||||
|
return bool(lhs.Cmp() < rhs.Cmp()) ^ reversed;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto start_it = entries.begin();
|
||||||
|
auto end_it = entries.end();
|
||||||
|
if (bounds) {
|
||||||
|
start_it += std::min(bounds->first, entries.size());
|
||||||
|
end_it = entries.begin() + std::min(bounds->first + bounds->second, entries.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
(*cntx)->StartArray(std::distance(start_it, end_it));
|
||||||
|
for (auto it = start_it; it != end_it; ++it) {
|
||||||
|
(*cntx)->SendBulkString(it->key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
std::visit(std::move(sort_call), entries.value());
|
||||||
|
}
|
||||||
|
|
||||||
void GenericFamily::Move(CmdArgList args, ConnectionContext* cntx) {
|
void GenericFamily::Move(CmdArgList args, ConnectionContext* cntx) {
|
||||||
string_view key = ArgS(args, 1);
|
string_view key = ArgS(args, 1);
|
||||||
int64_t target_db;
|
int64_t target_db;
|
||||||
|
@ -868,6 +1032,7 @@ void GenericFamily::Register(CommandRegistry* registry) {
|
||||||
<< CI{"TYPE", CO::READONLY | CO::FAST | CO::LOADING, 2, 1, 1, 1}.HFUNC(Type)
|
<< CI{"TYPE", CO::READONLY | CO::FAST | CO::LOADING, 2, 1, 1, 1}.HFUNC(Type)
|
||||||
<< CI{"UNLINK", CO::WRITE, -2, 1, -1, 1}.HFUNC(Del)
|
<< CI{"UNLINK", CO::WRITE, -2, 1, -1, 1}.HFUNC(Del)
|
||||||
<< CI{"STICK", CO::WRITE, -2, 1, -1, 1}.HFUNC(Stick)
|
<< CI{"STICK", CO::WRITE, -2, 1, -1, 1}.HFUNC(Stick)
|
||||||
|
<< CI{"SORT", CO::READONLY, -2, 1, 1, 1}.HFUNC(Sort)
|
||||||
<< CI{"MOVE", CO::WRITE | CO::GLOBAL_TRANS, 3, 1, 1, 1}.HFUNC(Move);
|
<< CI{"MOVE", CO::WRITE | CO::GLOBAL_TRANS, 3, 1, 1, 1}.HFUNC(Move);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ class GenericFamily {
|
||||||
static void Keys(CmdArgList args, ConnectionContext* cntx);
|
static void Keys(CmdArgList args, ConnectionContext* cntx);
|
||||||
static void PexpireAt(CmdArgList args, ConnectionContext* cntx);
|
static void PexpireAt(CmdArgList args, ConnectionContext* cntx);
|
||||||
static void Stick(CmdArgList args, ConnectionContext* cntx);
|
static void Stick(CmdArgList args, ConnectionContext* cntx);
|
||||||
|
static void Sort(CmdArgList args, ConnectionContext* cntx);
|
||||||
static void Move(CmdArgList args, ConnectionContext* cntx);
|
static void Move(CmdArgList args, ConnectionContext* cntx);
|
||||||
|
|
||||||
static void Rename(CmdArgList args, ConnectionContext* cntx);
|
static void Rename(CmdArgList args, ConnectionContext* cntx);
|
||||||
|
|
|
@ -160,7 +160,7 @@ TEST_F(GenericFamilyTest, RenameNx) {
|
||||||
Run({"mset", "x", x_val, "b", b_val});
|
Run({"mset", "x", x_val, "b", b_val});
|
||||||
|
|
||||||
ASSERT_THAT(Run({"renamenx", "z", "b"}), ErrArg("no such key"));
|
ASSERT_THAT(Run({"renamenx", "z", "b"}), ErrArg("no such key"));
|
||||||
ASSERT_THAT(Run({"renamenx", "x", "b"}), IntArg(0)); // b already exists
|
ASSERT_THAT(Run({"renamenx", "x", "b"}), IntArg(0)); // b already exists
|
||||||
ASSERT_THAT(Run({"renamenx", "x", "y"}), IntArg(1));
|
ASSERT_THAT(Run({"renamenx", "x", "y"}), IntArg(1));
|
||||||
ASSERT_EQ(Run({"get", "y"}), x_val);
|
ASSERT_EQ(Run({"get", "y"}), x_val);
|
||||||
}
|
}
|
||||||
|
@ -169,7 +169,7 @@ TEST_F(GenericFamilyTest, Stick) {
|
||||||
// check stick returns zero on non-existent keys
|
// check stick returns zero on non-existent keys
|
||||||
ASSERT_THAT(Run({"stick", "a", "b"}), IntArg(0));
|
ASSERT_THAT(Run({"stick", "a", "b"}), IntArg(0));
|
||||||
|
|
||||||
for (auto key: {"a", "b", "c", "d"}) {
|
for (auto key : {"a", "b", "c", "d"}) {
|
||||||
Run({"set", key, "."});
|
Run({"set", key, "."});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,4 +276,59 @@ TEST_F(GenericFamilyTest, Scan) {
|
||||||
EXPECT_THAT(vec, Each(StartsWith("zset")));
|
EXPECT_THAT(vec, Each(StartsWith("zset")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(GenericFamilyTest, Sort) {
|
||||||
|
// Test list sort with params
|
||||||
|
Run({"del", "list-1"});
|
||||||
|
Run({"lpush", "list-1", "3.5", "1.2", "10.1", "2.20", "200"});
|
||||||
|
// numeric
|
||||||
|
ASSERT_THAT(Run({"sort", "list-1"}).GetVec(), ElementsAre("1.2", "2.20", "3.5", "10.1", "200"));
|
||||||
|
// string
|
||||||
|
ASSERT_THAT(Run({"sort", "list-1", "ALPHA"}).GetVec(), ElementsAre("1.2", "10.1", "2.20", "200", "3.5"));
|
||||||
|
// desc numeric
|
||||||
|
ASSERT_THAT(Run({"sort", "list-1", "DESC"}).GetVec(), ElementsAre("200", "10.1", "3.5", "2.20", "1.2"));
|
||||||
|
// desc strig
|
||||||
|
ASSERT_THAT(Run({"sort", "list-1", "DESC", "ALPHA"}).GetVec(), ElementsAre("3.5", "200", "2.20", "10.1", "1.2"));
|
||||||
|
// limits
|
||||||
|
ASSERT_THAT(Run({"sort", "list-1", "LIMIT", "0", "5"}).GetVec(), ElementsAre("1.2", "2.20", "3.5", "10.1", "200"));
|
||||||
|
ASSERT_THAT(Run({"sort", "list-1", "LIMIT", "0", "10"}).GetVec(), ElementsAre("1.2", "2.20", "3.5", "10.1", "200"));
|
||||||
|
ASSERT_THAT(Run({"sort", "list-1", "LIMIT", "2", "2"}).GetVec(), ElementsAre("3.5", "10.1"));
|
||||||
|
ASSERT_THAT(Run({"sort", "list-1", "LIMIT", "1", "1"}), "2.20");
|
||||||
|
ASSERT_THAT(Run({"sort", "list-1", "LIMIT", "4", "2"}), "200");
|
||||||
|
ASSERT_THAT(Run({"sort", "list-1", "LIMIT", "5", "2"}), ArrLen(0));
|
||||||
|
// limits desc
|
||||||
|
ASSERT_THAT(Run({"sort", "list-1", "DESC", "LIMIT", "0", "5"}).GetVec(), ElementsAre("200", "10.1", "3.5", "2.20", "1.2"));
|
||||||
|
ASSERT_THAT(Run({"sort", "list-1", "DESC", "LIMIT", "2", "2"}).GetVec(), ElementsAre("3.5", "2.20"));
|
||||||
|
ASSERT_THAT(Run({"sort", "list-1", "DESC", "LIMIT", "1", "1"}), "10.1");
|
||||||
|
ASSERT_THAT(Run({"sort", "list-1", "DESC", "LIMIT", "5", "2"}), ArrLen(0));
|
||||||
|
|
||||||
|
// Test set sort
|
||||||
|
Run({"del", "set-1"});
|
||||||
|
Run({"sadd", "set-1", "5.3", "4.4", "60", "99.9", "100", "9"});
|
||||||
|
ASSERT_THAT(Run({"sort", "set-1"}).GetVec(), ElementsAre("4.4", "5.3", "9", "60", "99.9", "100"));
|
||||||
|
ASSERT_THAT(Run({"sort", "set-1", "ALPHA"}).GetVec(), ElementsAre("100", "4.4", "5.3", "60", "9", "99.9"));
|
||||||
|
ASSERT_THAT(Run({"sort", "set-1", "DESC"}).GetVec(), ElementsAre("100", "99.9", "60", "9", "5.3", "4.4"));
|
||||||
|
ASSERT_THAT(Run({"sort", "set-1", "DESC", "ALPHA"}).GetVec(), ElementsAre("99.9", "9", "60", "5.3", "4.4", "100"));
|
||||||
|
|
||||||
|
// Test intset sort
|
||||||
|
Run({"del", "intset-1"});
|
||||||
|
Run({"sadd", "intset-1", "5", "4", "3", "2", "1"});
|
||||||
|
ASSERT_THAT(Run({"sort", "intset-1"}).GetVec(), ElementsAre("1", "2", "3", "4", "5"));
|
||||||
|
|
||||||
|
// Test sorted set sort
|
||||||
|
Run({"del", "zset-1"});
|
||||||
|
Run({"zadd", "zset-1", "0", "3.3", "0", "30.1", "0", "8.2"});
|
||||||
|
ASSERT_THAT(Run({"sort", "zset-1"}).GetVec(), ElementsAre("3.3", "8.2", "30.1"));
|
||||||
|
ASSERT_THAT(Run({"sort", "zset-1", "ALPHA"}).GetVec(), ElementsAre("3.3", "30.1", "8.2"));
|
||||||
|
ASSERT_THAT(Run({"sort", "zset-1", "DESC"}).GetVec(), ElementsAre("30.1", "8.2", "3.3"));
|
||||||
|
ASSERT_THAT(Run({"sort", "zset-1", "DESC", "ALPHA"}).GetVec(), ElementsAre("8.2", "30.1", "3.3"));
|
||||||
|
|
||||||
|
// Test sort with non existent key
|
||||||
|
Run({"del", "list-2"});
|
||||||
|
ASSERT_THAT(Run({"sort", "list-2"}), ArrLen(0));
|
||||||
|
|
||||||
|
// Test not convertible to double
|
||||||
|
Run({"lpush", "list-2", "NOTADOUBLE"});
|
||||||
|
ASSERT_THAT(Run({"sort", "list-2"}), ErrArg("One or more scores can't be converted into double"));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace dfly
|
} // namespace dfly
|
||||||
|
|
|
@ -19,6 +19,7 @@ extern "C" {
|
||||||
#include "server/error.h"
|
#include "server/error.h"
|
||||||
#include "server/server_state.h"
|
#include "server/server_state.h"
|
||||||
#include "server/transaction.h"
|
#include "server/transaction.h"
|
||||||
|
#include "server/container_utils.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of entries allowed per internal list node can be specified
|
* The number of entries allowed per internal list node can be specified
|
||||||
|
@ -65,17 +66,6 @@ using absl::GetFlag;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
quicklistEntry QLEntry() {
|
|
||||||
quicklistEntry res{.quicklist = NULL,
|
|
||||||
.node = NULL,
|
|
||||||
.zi = NULL,
|
|
||||||
.value = NULL,
|
|
||||||
.longval = 0,
|
|
||||||
.sz = 0,
|
|
||||||
.offset = 0};
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
quicklist* GetQL(const PrimeValue& mv) {
|
quicklist* GetQL(const PrimeValue& mv) {
|
||||||
return (quicklist*)mv.RObjPtr();
|
return (quicklist*)mv.RObjPtr();
|
||||||
}
|
}
|
||||||
|
@ -358,7 +348,7 @@ OpResult<string> RPeek(const OpArgs& op_args, string_view key, bool fetch) {
|
||||||
return OpStatus::OK;
|
return OpStatus::OK;
|
||||||
|
|
||||||
quicklist* ql = GetQL(it_res.value()->second);
|
quicklist* ql = GetQL(it_res.value()->second);
|
||||||
quicklistEntry entry = QLEntry();
|
quicklistEntry entry = container_utils::QLEntry();
|
||||||
quicklistIter* iter = quicklistGetIterator(ql, AL_START_TAIL);
|
quicklistIter* iter = quicklistGetIterator(ql, AL_START_TAIL);
|
||||||
CHECK(quicklistNext(iter, &entry));
|
CHECK(quicklistNext(iter, &entry));
|
||||||
quicklistReleaseIterator(iter);
|
quicklistReleaseIterator(iter);
|
||||||
|
@ -845,7 +835,7 @@ OpResult<string> ListFamily::OpIndex(const OpArgs& op_args, std::string_view key
|
||||||
if (!res)
|
if (!res)
|
||||||
return res.status();
|
return res.status();
|
||||||
quicklist* ql = GetQL(res.value()->second);
|
quicklist* ql = GetQL(res.value()->second);
|
||||||
quicklistEntry entry = QLEntry();
|
quicklistEntry entry = container_utils::QLEntry();
|
||||||
quicklistIter* iter = quicklistGetIteratorAtIdx(ql, AL_START_TAIL, index);
|
quicklistIter* iter = quicklistGetIteratorAtIdx(ql, AL_START_TAIL, index);
|
||||||
if (!iter)
|
if (!iter)
|
||||||
return OpStatus::KEY_NOTFOUND;
|
return OpStatus::KEY_NOTFOUND;
|
||||||
|
@ -871,7 +861,7 @@ OpResult<int> ListFamily::OpInsert(const OpArgs& op_args, string_view key, strin
|
||||||
return it_res.status();
|
return it_res.status();
|
||||||
|
|
||||||
quicklist* ql = GetQL(it_res.value()->second);
|
quicklist* ql = GetQL(it_res.value()->second);
|
||||||
quicklistEntry entry = QLEntry();
|
quicklistEntry entry = container_utils::QLEntry();
|
||||||
quicklistIter* qiter = quicklistGetIterator(ql, AL_START_HEAD);
|
quicklistIter* qiter = quicklistGetIterator(ql, AL_START_HEAD);
|
||||||
bool found = false;
|
bool found = false;
|
||||||
|
|
||||||
|
@ -1030,24 +1020,12 @@ OpResult<StringVec> ListFamily::OpRange(const OpArgs& op_args, std::string_view
|
||||||
return StringVec{};
|
return StringVec{};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (end >= llen)
|
|
||||||
end = llen - 1;
|
|
||||||
|
|
||||||
unsigned lrange = end - start + 1;
|
|
||||||
quicklistIter* qiter = quicklistGetIteratorAtIdx(ql, AL_START_HEAD, start);
|
|
||||||
quicklistEntry entry = QLEntry();
|
|
||||||
StringVec str_vec;
|
StringVec str_vec;
|
||||||
|
container_utils::IterateList(res.value()->second, [&str_vec](container_utils::ContainerEntry ce) {
|
||||||
unsigned cnt = 0;
|
str_vec.emplace_back(ce.ToString());
|
||||||
while (cnt < lrange && quicklistNext(qiter, &entry)) {
|
return true;
|
||||||
if (entry.value)
|
}, start, end);
|
||||||
str_vec.emplace_back(reinterpret_cast<char*>(entry.value), entry.sz);
|
|
||||||
else
|
|
||||||
str_vec.push_back(absl::StrCat(entry.longval));
|
|
||||||
++cnt;
|
|
||||||
}
|
|
||||||
quicklistReleaseIterator(qiter);
|
|
||||||
|
|
||||||
return str_vec;
|
return str_vec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1439,7 +1439,7 @@ void ServerFamily::Latency(CmdArgList args, ConnectionContext* cntx) {
|
||||||
string_view sub_cmd = ArgS(args, 1);
|
string_view sub_cmd = ArgS(args, 1);
|
||||||
|
|
||||||
if (sub_cmd == "LATEST") {
|
if (sub_cmd == "LATEST") {
|
||||||
return (*cntx)->StartArray(0);
|
return (*cntx)->SendEmptyArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_FIRST_N(ERROR, 10) << "Subcommand " << sub_cmd << " not supported";
|
LOG_FIRST_N(ERROR, 10) << "Subcommand " << sub_cmd << " not supported";
|
||||||
|
|
|
@ -21,6 +21,8 @@ extern "C" {
|
||||||
#include "server/error.h"
|
#include "server/error.h"
|
||||||
#include "server/transaction.h"
|
#include "server/transaction.h"
|
||||||
|
|
||||||
|
#include "server/container_utils.h"
|
||||||
|
|
||||||
ABSL_DECLARE_FLAG(bool, use_set2);
|
ABSL_DECLARE_FLAG(bool, use_set2);
|
||||||
|
|
||||||
namespace dfly {
|
namespace dfly {
|
||||||
|
@ -146,27 +148,6 @@ void InitStrSet(CompactObj* set) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// f receives a str object.
|
|
||||||
template <typename F> void FillFromStrSet(F&& f, void* ptr) {
|
|
||||||
string str;
|
|
||||||
if (GetFlag(FLAGS_use_set2)) {
|
|
||||||
for (sds ptr : *(StringSet*)ptr) {
|
|
||||||
str.assign(ptr, sdslen(ptr));
|
|
||||||
f(move(str));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dict* ds = (dict*)ptr;
|
|
||||||
|
|
||||||
dictIterator* di = dictGetIterator(ds);
|
|
||||||
dictEntry* de = nullptr;
|
|
||||||
while ((de = dictNext(di))) {
|
|
||||||
str.assign((sds)de->key, sdslen((sds)de->key));
|
|
||||||
f(move(str));
|
|
||||||
}
|
|
||||||
dictReleaseIterator(di);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns (removed, isempty)
|
// returns (removed, isempty)
|
||||||
pair<unsigned, bool> RemoveSet(ArgSlice vals, CompactObj* set) {
|
pair<unsigned, bool> RemoveSet(ArgSlice vals, CompactObj* set) {
|
||||||
bool isempty = false;
|
bool isempty = false;
|
||||||
|
@ -493,22 +474,6 @@ OpStatus NoOpCb(Transaction* t, EngineShard* shard) {
|
||||||
return OpStatus::OK;
|
return OpStatus::OK;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename F> void FillSet(const SetType& set, F&& f) {
|
|
||||||
if (set.second == kEncodingIntSet) {
|
|
||||||
intset* is = (intset*)set.first;
|
|
||||||
int64_t ival;
|
|
||||||
int ii = 0;
|
|
||||||
char buf[32];
|
|
||||||
|
|
||||||
while (intsetGet(is, ii++, &ival)) {
|
|
||||||
char* next = absl::numbers_internal::FastIntToBuffer(ival, buf);
|
|
||||||
f(string{buf, size_t(next - buf)});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
FillFromStrSet(move(f), set.first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if overwrite is true then OpAdd writes vals into the key and discards its previous value.
|
// if overwrite is true then OpAdd writes vals into the key and discards its previous value.
|
||||||
OpResult<uint32_t> OpAdd(const OpArgs& op_args, std::string_view key, ArgSlice vals,
|
OpResult<uint32_t> OpAdd(const OpArgs& op_args, std::string_view key, ArgSlice vals,
|
||||||
bool overwrite) {
|
bool overwrite) {
|
||||||
|
@ -713,8 +678,10 @@ OpResult<StringVec> OpUnion(const OpArgs& op_args, ArgSlice keys) {
|
||||||
for (std::string_view key : keys) {
|
for (std::string_view key : keys) {
|
||||||
OpResult<PrimeIterator> find_res = op_args.shard->db_slice().Find(op_args.db_ind, key, OBJ_SET);
|
OpResult<PrimeIterator> find_res = op_args.shard->db_slice().Find(op_args.db_ind, key, OBJ_SET);
|
||||||
if (find_res) {
|
if (find_res) {
|
||||||
SetType st{find_res.value()->second.RObjPtr(), find_res.value()->second.Encoding()};
|
container_utils::IterateSet(find_res.value()->second, [&uniques](container_utils::ContainerEntry ce){
|
||||||
FillSet(st, [&uniques](string s) { uniques.emplace(move(s)); });
|
uniques.emplace(ce.ToString());
|
||||||
|
return true;
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -738,9 +705,10 @@ OpResult<StringVec> OpDiff(const OpArgs& op_args, ArgSlice keys) {
|
||||||
}
|
}
|
||||||
|
|
||||||
absl::flat_hash_set<string> uniques;
|
absl::flat_hash_set<string> uniques;
|
||||||
SetType st{find_res.value()->second.RObjPtr(), find_res.value()->second.Encoding()};
|
container_utils::IterateSet(find_res.value()->second, [&uniques](container_utils::ContainerEntry ce) {
|
||||||
|
uniques.emplace(ce.ToString());
|
||||||
FillSet(st, [&uniques](string s) { uniques.insert(move(s)); });
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
DCHECK(!uniques.empty()); // otherwise the key would not exist.
|
DCHECK(!uniques.empty()); // otherwise the key would not exist.
|
||||||
|
|
||||||
|
@ -786,9 +754,10 @@ OpResult<StringVec> OpInter(const Transaction* t, EngineShard* es, bool remove_f
|
||||||
if (!find_res)
|
if (!find_res)
|
||||||
return find_res.status();
|
return find_res.status();
|
||||||
|
|
||||||
SetType st{find_res.value()->second.RObjPtr(), find_res.value()->second.Encoding()};
|
container_utils::IterateSet(find_res.value()->second, [&result](container_utils::ContainerEntry ce) {
|
||||||
|
result.push_back(ce.ToString());
|
||||||
FillSet(st, [&result](string s) { result.push_back(move(s)); });
|
return true;
|
||||||
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1261,16 +1230,20 @@ OpResult<StringVec> SetFamily::OpPop(const OpArgs& op_args, std::string_view key
|
||||||
|
|
||||||
PrimeIterator it = find_res.value();
|
PrimeIterator it = find_res.value();
|
||||||
size_t slen = it->second.Size();
|
size_t slen = it->second.Size();
|
||||||
SetType st{it->second.RObjPtr(), it->second.Encoding()};
|
|
||||||
|
|
||||||
/* CASE 1:
|
/* CASE 1:
|
||||||
* The number of requested elements is greater than or equal to
|
* The number of requested elements is greater than or equal to
|
||||||
* the number of elements inside the set: simply return the whole set. */
|
* the number of elements inside the set: simply return the whole set. */
|
||||||
if (count >= slen) {
|
if (count >= slen) {
|
||||||
FillSet(st, [&result](string s) { result.push_back(move(s)); });
|
container_utils::IterateSet(it->second, [&result](container_utils::ContainerEntry ce) {
|
||||||
|
result.push_back(ce.ToString());
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
/* Delete the set as it is now empty */
|
/* Delete the set as it is now empty */
|
||||||
CHECK(db_slice.Del(op_args.db_ind, it));
|
CHECK(db_slice.Del(op_args.db_ind, it));
|
||||||
} else {
|
} else {
|
||||||
|
SetType st{it->second.RObjPtr(), it->second.Encoding()};
|
||||||
db_slice.PreUpdate(op_args.db_ind, it);
|
db_slice.PreUpdate(op_args.db_ind, it);
|
||||||
if (st.second == kEncodingIntSet) {
|
if (st.second == kEncodingIntSet) {
|
||||||
intset* is = (intset*)st.first;
|
intset* is = (intset*)st.first;
|
||||||
|
|
|
@ -847,7 +847,7 @@ void StreamFamily::XRangeGeneric(CmdArgList args, bool is_rev, ConnectionContext
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.status() == OpStatus::KEY_NOTFOUND) {
|
if (result.status() == OpStatus::KEY_NOTFOUND) {
|
||||||
return (*cntx)->StartArray(0);
|
return (*cntx)->SendEmptyArray();
|
||||||
}
|
}
|
||||||
return (*cntx)->SendError(result.status());
|
return (*cntx)->SendError(result.status());
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ extern "C" {
|
||||||
#include "server/conn_context.h"
|
#include "server/conn_context.h"
|
||||||
#include "server/engine_shard_set.h"
|
#include "server/engine_shard_set.h"
|
||||||
#include "server/transaction.h"
|
#include "server/transaction.h"
|
||||||
|
#include "server/container_utils.h"
|
||||||
|
|
||||||
namespace dfly {
|
namespace dfly {
|
||||||
|
|
||||||
|
@ -244,60 +245,10 @@ void IntervalVisitor::operator()(const ZSetFamily::LexInterval& li) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntervalVisitor::ActionRange(unsigned start, unsigned end) {
|
void IntervalVisitor::ActionRange(unsigned start, unsigned end) {
|
||||||
unsigned rangelen = (end - start) + 1;
|
container_utils::IterateSortedSet(zobj_, [this](container_utils::ContainerEntry ce, double score){
|
||||||
|
result_.emplace_back(ce.ToString(), score);
|
||||||
if (zobj_->encoding == OBJ_ENCODING_LISTPACK) {
|
return true;
|
||||||
uint8_t* zl = (uint8_t*)zobj_->ptr;
|
}, start, end, params_.reverse, params_.with_scores);
|
||||||
uint8_t *eptr, *sptr;
|
|
||||||
uint8_t* vstr;
|
|
||||||
unsigned int vlen;
|
|
||||||
long long vlong;
|
|
||||||
double score = 0.0;
|
|
||||||
|
|
||||||
if (params_.reverse)
|
|
||||||
eptr = lpSeek(zl, -2 - long(2 * start));
|
|
||||||
else
|
|
||||||
eptr = lpSeek(zl, 2 * start);
|
|
||||||
DCHECK(eptr);
|
|
||||||
|
|
||||||
sptr = lpNext(zl, eptr);
|
|
||||||
|
|
||||||
while (rangelen--) {
|
|
||||||
DCHECK(eptr != NULL && sptr != NULL);
|
|
||||||
vstr = lpGetValue(eptr, &vlen, &vlong);
|
|
||||||
|
|
||||||
if (params_.with_scores) /* don't bother to extract the score if it's gonna be ignored. */
|
|
||||||
score = zzlGetScore(sptr);
|
|
||||||
|
|
||||||
AddResult(vstr, vlen, vlong, score);
|
|
||||||
|
|
||||||
Next(zl, &eptr, &sptr);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
CHECK_EQ(zobj_->encoding, OBJ_ENCODING_SKIPLIST);
|
|
||||||
zset* zs = (zset*)zobj_->ptr;
|
|
||||||
zskiplist* zsl = zs->zsl;
|
|
||||||
zskiplistNode* ln;
|
|
||||||
|
|
||||||
/* Check if starting point is trivial, before doing log(N) lookup. */
|
|
||||||
if (params_.reverse) {
|
|
||||||
ln = zsl->tail;
|
|
||||||
unsigned long llen = zsetLength(zobj_);
|
|
||||||
if (start > 0)
|
|
||||||
ln = zslGetElementByRank(zsl, llen - start);
|
|
||||||
} else {
|
|
||||||
ln = zsl->header->level[0].forward;
|
|
||||||
if (start > 0)
|
|
||||||
ln = zslGetElementByRank(zsl, start + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (rangelen--) {
|
|
||||||
DCHECK(ln != NULL);
|
|
||||||
sds ele = ln->ele;
|
|
||||||
result_.emplace_back(string(ele, sdslen(ele)), ln->score);
|
|
||||||
ln = Next(ln);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntervalVisitor::ActionRange(const zrangespec& range) {
|
void IntervalVisitor::ActionRange(const zrangespec& range) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue