Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: hrandfield #10

Closed
wants to merge 9 commits into from
1 change: 1 addition & 0 deletions src/base_cmd.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const std::string kCmdNameHGetAll = "hgetall";
const std::string kCmdNameHKeys = "hkeys";
const std::string kCmdNameHLen = "hlen";
const std::string kCmdNameHStrLen = "hstrlen";
const std::string kCmdNameHRandField = "hrandfield";

// set cmd
const std::string kCmdNameSIsMember = "sismember";
Expand Down
65 changes: 65 additions & 0 deletions src/cmd_hash.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <config.h>

#include "pstd/pstd_string.h"
#include "store.h"

namespace pikiwidb {
Expand Down Expand Up @@ -227,4 +228,68 @@ void HStrLenCmd::DoCmd(PClient* client) {
}
}

HRandFieldCmd::HRandFieldCmd(const std::string& name, int16_t arity)
: BaseCmd(name, arity, kCmdFlagsReadonly, kAclCategoryRead | kAclCategoryHash) {}

bool HRandFieldCmd::DoInitial(PClient* client) {
/*
* There should not be quantity detection here,
* because the quantity detection of redis is after the COUNT integer detection.
*/
// if (client->argv_.size() > 4) {
// client->SetRes(CmdRes::kSyntaxErr);
// return false;
// }
client->SetKey(client->argv_[1]);
return true;
}

void HRandFieldCmd::DoCmd(PClient* client) {
// parse arguments
const auto& argv = client->argv_;
int64_t count{1};
bool with_values{false};
if (argv.size() > 2) {
// redis checks the integer argument first and then the number of parameters
if (pstd::String2int(argv[2], &count) == 0) {
client->SetRes(CmdRes::kInvalidInt);
return;
}
if (argv.size() > 4) {
client->SetRes(CmdRes::kSyntaxErr);
return;
}
if (argv.size() > 3) {
if (kWithValueString != pstd::StringToLower(argv[3])) {
client->SetRes(CmdRes::kSyntaxErr);
return;
}
with_values = true;
}
}

// execute command
std::vector<storage::FieldValue> fvs;
auto s = PSTORE.GetBackend()->HRandField(client->Key(), count, &fvs);
if (s.IsNotFound()) {
client->AppendString("");
return;
}
if (!s.ok()) {
client->SetRes(CmdRes::kErrOther, s.ToString());
return;
}

// reply to client
if (argv.size() > 2) {
client->AppendArrayLenUint64(with_values ? fvs.size() * 2 : fvs.size());
}
for (const auto& [field, value] : fvs) {
client->AppendString(field);
if (with_values) {
client->AppendString(value);
}
}
}

} // namespace pikiwidb
16 changes: 16 additions & 0 deletions src/cmd_hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

#pragma once

#include <random>

#include "base_cmd.h"
#include "hash.h"

namespace pikiwidb {

Expand Down Expand Up @@ -99,4 +102,17 @@ class HStrLenCmd : public BaseCmd {
void DoCmd(PClient *client) override;
};

class HRandFieldCmd : public BaseCmd {
public:
HRandFieldCmd(const std::string &name, int16_t arity);

protected:
bool DoInitial(PClient *client) override;

private:
void DoCmd(PClient *client) override;

static constexpr const char *kWithValueString = "withvalues";
};

} // namespace pikiwidb
1 change: 1 addition & 0 deletions src/cmd_table_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ void CmdTableManager::InitCmdTable() {
ADD_COMMAND(HKeys, 2);
ADD_COMMAND(HLen, 2);
ADD_COMMAND(HStrLen, 3);
ADD_COMMAND(HRandField, -2);

// set
ADD_COMMAND(SIsMember, 3);
Expand Down
3 changes: 3 additions & 0 deletions src/storage/include/storage/storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,9 @@ class Storage {
Status HScanx(const Slice& key, const std::string& start_field, const std::string& pattern, int64_t count,
std::vector<FieldValue>* field_values, std::string* next_field);

// Return random field(s) and value(s) from the hash value stored at key.
Status HRandField(const Slice& key, int64_t count, std::vector<FieldValue>* fvs);

// Iterate over a Hash table of fields by specified range
// return next_field that the user need to use as the start_field argument
// in the next call
Expand Down
53 changes: 53 additions & 0 deletions src/storage/src/redis_hashes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include "src/redis_hashes.h"

#include <memory>
#include <numeric>
#include <random>

#include <fmt/core.h>
#include <glog/logging.h>
Expand Down Expand Up @@ -923,6 +925,57 @@ Status RedisHashes::HScanx(const Slice& key, const std::string& start_field, con
return Status::OK();
}

Status RedisHashes::HRandField(const Slice& key, int64_t count, std::vector<FieldValue>* fvs) {
std::string meta_value;
Status s = db_->Get(default_read_options_, handles_[0], key, &meta_value);
if (!s.ok()) {
return s;
}
ParsedHashesMetaValue parsed_hashes_meta_value(&meta_value);
auto hlen = parsed_hashes_meta_value.count();
if (parsed_hashes_meta_value.IsStale() || hlen == 0) {
return Status::NotFound();
}

if (count >= hlen) {
// case 1: count > 0 and >= hlen, return all fv
return HGetall(key, fvs);
}

std::vector<uint32_t> idxs;
if (count < 0) {
// case 2: count < 0, allow duplication
while (idxs.size() < -count) {
idxs.push_back(rand() % hlen);
}
} else {
// case 3: count > 0 and < hlen, no duplication
std::vector<uint32_t> range(hlen);
std::iota(range.begin(), range.end(), 0);
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(range.begin(), range.end(), g);
idxs.insert(idxs.cend(), range.begin(), range.begin() + count);
}
std::sort(idxs.begin(), idxs.end());

HashesDataKey hashes_data_key(key, parsed_hashes_meta_value.version(), "");
Slice prefix = hashes_data_key.Encode();
auto iter = db_->NewIterator(default_read_options_, handles_[1]);
iter->Seek(prefix);
uint32_t save_idx{};
for (auto idx : idxs) {
while (save_idx < idx) {
iter->Next();
save_idx++;
}
assert(iter->Valid());
ParsedHashesDataKey datakey(iter->key());
fvs->push_back({datakey.field().ToString(), iter->value().ToString()});
}
return Status::OK();
}

Status RedisHashes::PKHScanRange(const Slice& key, const Slice& field_start, const std::string& field_end,
const Slice& pattern, int32_t limit, std::vector<FieldValue>* field_values,
std::string* next_field) {
Expand Down
1 change: 1 addition & 0 deletions src/storage/src/redis_hashes.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class RedisHashes : public Redis {
std::vector<FieldValue>* field_values, int64_t* next_cursor);
Status HScanx(const Slice& key, const std::string& start_field, const std::string& pattern, int64_t count,
std::vector<FieldValue>* field_values, std::string* next_field);
Status HRandField(const Slice& key, int64_t count, std::vector<FieldValue>* fvs);
Status PKHScanRange(const Slice& key, const Slice& field_start, const std::string& field_end, const Slice& pattern,
int32_t limit, std::vector<FieldValue>* field_values, std::string* next_field);
Status PKHRScanRange(const Slice& key, const Slice& field_start, const std::string& field_end, const Slice& pattern,
Expand Down
4 changes: 4 additions & 0 deletions src/storage/src/storage.cc
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ Status Storage::HScanx(const Slice& key, const std::string& start_field, const s
return hashes_db_->HScanx(key, start_field, pattern, count, field_values, next_field);
}

Status Storage::HRandField(const Slice& key, int64_t count, std::vector<FieldValue>* fvs) {
return hashes_db_->HRandField(key, count, fvs);
}

Status Storage::PKHScanRange(const Slice& key, const Slice& field_start, const std::string& field_end,
const Slice& pattern, int32_t limit, std::vector<FieldValue>* field_values,
std::string* next_field) {
Expand Down