1
0
Fork 0
mirror of https://github.com/dragonflydb/dragonfly.git synced 2024-12-15 17:51:06 +00:00
dragonflydb-dragonfly/server/list_family.cc
Roman Gershman d64b4e01ea Implement basic list operations
Fix redis linker dependencies. Bring more redis (BSD-3) code.
Fix bugs related to multi-database redis.
Introduce multiple types for redis values.
2022-01-02 10:40:16 +02:00

282 lines
8.7 KiB
C++

// Copyright 2021, Roman Gershman. All rights reserved.
// See LICENSE for licensing terms.
//
#include "server/list_family.h"
extern "C" {
#include "redis/object.h"
#include "redis/sds.h"
}
#include <absl/strings/numbers.h>
#include "base/logging.h"
#include "server/command_registry.h"
#include "server/conn_context.h"
#include "server/engine_shard_set.h"
#include "server/error.h"
#include "server/transaction.h"
/**
* The number of entries allowed per internal list node can be specified
* as a fixed maximum size or a maximum number of elements.
* For a fixed maximum size, use -5 through -1, meaning:
* -5: max size: 64 Kb <-- not recommended for normal workloads
* -4: max size: 32 Kb <-- not recommended
* -3: max size: 16 Kb <-- probably not recommended
* -2: max size: 8 Kb <-- good
* -1: max size: 4 Kb <-- good
* Positive numbers mean store up to _exactly_ that number of elements
* per list node.
* The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size),
* but if your use case is unique, adjust the settings as necessary.
*
*/
DEFINE_int32(list_max_listpack_size, -2, "Maximum ziplist size, default is 8kb");
/**
* Lists may also be compressed.
* Compress depth is the number of quicklist ziplist nodes from *each* side of
* the list to *exclude* from compression. The head and tail of the list
* are always uncompressed for fast push/pop operations. Settings are:
* 0: disable all list compression
* 1: depth 1 means "don't start compressing until after 1 node into the list,
* going from either the head or tail"
* So: [head]->node->node->...->node->[tail]
* [head], [tail] will always be uncompressed; inner nodes will compress.
* 2: [head]->[next]->node->node->...->node->[prev]->[tail]
* 2 here means: don't compress head or head->next or tail->prev or tail,
* but compress all nodes between them.
* 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail]
* etc.
*
*/
DEFINE_int32(list_compress_depth, 0, "Compress depth of the list. Default is no compression");
namespace dfly {
using namespace std;
namespace {
quicklist* GetQL(const MainValue& mv) {
DCHECK(mv.robj);
robj* o = (robj*)mv.robj;
return (quicklist*)o->ptr;
}
void* listPopSaver(unsigned char* data, size_t sz) {
return createStringObject((char*)data, sz);
}
OpResult<string> ListPop(DbIndex db_ind, const MainValue& mv, ListDir dir) {
VLOG(1) << "Pop db_indx " << db_ind;
if (mv.ObjType() != OBJ_LIST)
return OpStatus::WRONG_TYPE;
long long vlong;
robj* value = NULL;
int ql_where = (dir == ListDir::LEFT) ? QUICKLIST_HEAD : QUICKLIST_TAIL;
quicklist* ql = GetQL(mv);
// Empty list automatically removes the key (see below).
CHECK_EQ(1,
quicklistPopCustom(ql, ql_where, (unsigned char**)&value, NULL, &vlong, listPopSaver));
string res;
if (value) {
DCHECK(value->encoding == OBJ_ENCODING_EMBSTR || value->encoding == OBJ_ENCODING_RAW);
sds s = (sds)(value->ptr);
res = string{s, sdslen(s)};
decrRefCount(value);
} else {
res = absl::StrCat(vlong);
}
return res;
}
} // namespace
void ListFamily::LPush(CmdArgList args, ConnectionContext* cntx) {
return PushGeneric(ListDir::LEFT, std::move(args), cntx);
}
void ListFamily::LPop(CmdArgList args, ConnectionContext* cntx) {
return PopGeneric(ListDir::LEFT, std::move(args), cntx);
}
void ListFamily::RPush(CmdArgList args, ConnectionContext* cntx) {
return PushGeneric(ListDir::RIGHT, std::move(args), cntx);
}
void ListFamily::RPop(CmdArgList args, ConnectionContext* cntx) {
return PopGeneric(ListDir::RIGHT, std::move(args), cntx);
}
void ListFamily::LLen(CmdArgList args, ConnectionContext* cntx) {
auto key = ArgS(args, 1);
auto cb = [&](Transaction* t, EngineShard* shard) {
return OpLen(OpArgs{shard, t->db_index()}, key);
};
OpResult<uint32_t> result = cntx->transaction->ScheduleSingleHopT(std::move(cb));
if (result) {
cntx->SendLong(result.value());
} else if (result.status() == OpStatus::KEY_NOTFOUND) {
cntx->SendLong(0);
} else {
cntx->SendError(result.status());
}
}
void ListFamily::LIndex(CmdArgList args, ConnectionContext* cntx) {
std::string_view key = ArgS(args, 1);
std::string_view index_str = ArgS(args, 2);
int32_t index;
if (!absl::SimpleAtoi(index_str, &index)) {
cntx->SendError(kInvalidIntErr);
return;
}
auto cb = [&](Transaction* t, EngineShard* shard) {
return OpIndex(OpArgs{shard, t->db_index()}, key, index);
};
OpResult<string> result = cntx->transaction->ScheduleSingleHopT(std::move(cb));
if (result) {
cntx->SendBulkString(result.value());
} else {
cntx->SendNull();
}
}
void ListFamily::PushGeneric(ListDir dir, const CmdArgList& args, ConnectionContext* cntx) {
std::string_view key = ArgS(args, 1);
vector<std::string_view> vals(args.size() - 2);
for (size_t i = 2; i < args.size(); ++i) {
vals[i - 2] = ArgS(args, i);
}
absl::Span<std::string_view> span{vals.data(), vals.size()};
auto cb = [&](Transaction* t, EngineShard* shard) {
return OpPush(OpArgs{shard, t->db_index()}, key, dir, span);
};
OpResult<uint32_t> result = cntx->transaction->ScheduleSingleHopT(std::move(cb));
switch (result.status()) {
case OpStatus::KEY_NOTFOUND:
return cntx->SendNull();
case OpStatus::WRONG_TYPE:
return cntx->SendError(kWrongTypeErr);
default:;
}
return cntx->SendLong(result.value());
}
void ListFamily::PopGeneric(ListDir dir, const CmdArgList& args, ConnectionContext* cntx) {
std::string_view key = ArgS(args, 1);
auto cb = [&](Transaction* t, EngineShard* shard) {
return OpPop(OpArgs{shard, t->db_index()}, key, dir);
};
OpResult<string> result = cntx->transaction->ScheduleSingleHopT(std::move(cb));
switch (result.status()) {
case OpStatus::KEY_NOTFOUND:
return cntx->SendNull();
case OpStatus::WRONG_TYPE:
return cntx->SendError(kWrongTypeErr);
default:;
}
return cntx->SendBulkString(result.value());
}
OpResult<uint32_t> ListFamily::OpPush(const OpArgs& op_args, std::string_view key, ListDir dir,
const absl::Span<std::string_view>& vals) {
EngineShard* es = op_args.shard;
auto [it, new_key] = es->db_slice().AddOrFind(op_args.db_ind, key);
quicklist* ql;
if (new_key) {
robj* o = createQuicklistObject();
ql = (quicklist*)o->ptr;
quicklistSetOptions(ql, FLAGS_list_max_listpack_size, FLAGS_list_compress_depth);
it->second.robj = o;
it->second.obj_type = OBJ_LIST;
} else {
if (it->second.ObjType() != OBJ_LIST)
return OpStatus::WRONG_TYPE;
ql = GetQL(it->second);
}
// Left push is LIST_HEAD.
int pos = (dir == ListDir::LEFT) ? QUICKLIST_HEAD : QUICKLIST_TAIL;
for (auto v : vals) {
es->tmp_str = sdscpylen(es->tmp_str, v.data(), v.size());
quicklistPush(ql, es->tmp_str, sdslen(es->tmp_str), pos);
}
return quicklistCount(ql);
}
OpResult<string> ListFamily::OpPop(const OpArgs& op_args, string_view key, ListDir dir) {
auto& db_slice = op_args.shard->db_slice();
auto [it, expire] = db_slice.FindExt(op_args.db_ind, key);
if (it == MainIterator{})
return OpStatus::KEY_NOTFOUND;
OpResult<string> res = ListPop(op_args.db_ind, it->second, dir);
if (res) {
quicklist* ql = GetQL(it->second);
if (quicklistCount(ql) == 0) {
CHECK(db_slice.Del(op_args.db_ind, it));
}
}
return res;
}
OpResult<uint32_t> ListFamily::OpLen(const OpArgs& op_args, std::string_view key) {
auto res = op_args.shard->db_slice().Find(op_args.db_ind, key, OBJ_LIST);
if (!res)
return res.status();
quicklist* ql = GetQL(res.value()->second);
return quicklistCount(ql);
}
OpResult<string> ListFamily::OpIndex(const OpArgs& op_args, std::string_view key, long index) {
auto res = op_args.shard->db_slice().Find(op_args.db_ind, key, OBJ_LIST);
if (!res)
return res.status();
quicklist* ql = GetQL(res.value()->second);
quicklistEntry entry;
quicklistIter* iter = quicklistGetIteratorEntryAtIdx(ql, index, &entry);
if (!iter)
return OpStatus::KEY_NOTFOUND;
if (entry.value) {
return string{reinterpret_cast<char*>(entry.value), entry.sz};
} else {
return absl::StrCat(entry.longval);
}
}
using CI = CommandId;
#define HFUNC(x) SetHandler(&ListFamily::x)
void ListFamily::Register(CommandRegistry* registry) {
*registry << CI{"LPUSH", CO::WRITE | CO::FAST | CO::DENYOOM, -3, 1, 1, 1}.HFUNC(LPush)
<< CI{"LPOP", CO::WRITE | CO::FAST | CO::DENYOOM, 2, 1, 1, 1}.HFUNC(LPop)
<< CI{"RPUSH", CO::WRITE | CO::FAST | CO::DENYOOM, -3, 1, 1, 1}.HFUNC(RPush)
<< CI{"RPOP", CO::WRITE | CO::FAST | CO::DENYOOM, 2, 1, 1, 1}.HFUNC(RPop)
<< CI{"LLEN", CO::READONLY | CO::FAST, 2, 1, 1, 1}.HFUNC(LLen)
<< CI{"LINDEX", CO::READONLY, 3, 1, 1, 1}.HFUNC(LIndex);
}
} // namespace dfly