redis++ binaries for mac

This commit is contained in:
Grant Limberg 2020-05-11 15:24:13 -07:00
commit 8f3a0b17ad
No known key found for this signature in database
GPG key ID: 2BA62CCABBB4095A
96 changed files with 35078 additions and 0 deletions

View file

@ -0,0 +1,376 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "command.h"
#include <cassert>
namespace sw {
namespace redis {
namespace cmd {
// KEY commands.
void restore(Connection &connection,
const StringView &key,
const StringView &val,
long long ttl,
bool replace) {
CmdArgs args;
args << "RESTORE" << key << ttl << val;
if (replace) {
args << "REPLACE";
}
connection.send(args);
}
// STRING commands.
void bitop(Connection &connection,
BitOp op,
const StringView &destination,
const StringView &key) {
CmdArgs args;
detail::set_bitop(args, op);
args << destination << key;
connection.send(args);
}
void set(Connection &connection,
const StringView &key,
const StringView &val,
long long ttl,
UpdateType type) {
CmdArgs args;
args << "SET" << key << val;
if (ttl > 0) {
args << "PX" << ttl;
}
detail::set_update_type(args, type);
connection.send(args);
}
// LIST commands.
void linsert(Connection &connection,
const StringView &key,
InsertPosition position,
const StringView &pivot,
const StringView &val) {
std::string pos;
switch (position) {
case InsertPosition::BEFORE:
pos = "BEFORE";
break;
case InsertPosition::AFTER:
pos = "AFTER";
break;
default:
assert(false);
}
connection.send("LINSERT %b %s %b %b",
key.data(), key.size(),
pos.c_str(),
pivot.data(), pivot.size(),
val.data(), val.size());
}
// GEO commands.
void geodist(Connection &connection,
const StringView &key,
const StringView &member1,
const StringView &member2,
GeoUnit unit) {
CmdArgs args;
args << "GEODIST" << key << member1 << member2;
detail::set_geo_unit(args, unit);
connection.send(args);
}
void georadius_store(Connection &connection,
const StringView &key,
const std::pair<double, double> &loc,
double radius,
GeoUnit unit,
const StringView &destination,
bool store_dist,
long long count) {
CmdArgs args;
args << "GEORADIUS" << key << loc.first << loc.second;
detail::set_georadius_store_parameters(args,
radius,
unit,
destination,
store_dist,
count);
connection.send(args);
}
void georadius(Connection &connection,
const StringView &key,
const std::pair<double, double> &loc,
double radius,
GeoUnit unit,
long long count,
bool asc,
bool with_coord,
bool with_dist,
bool with_hash) {
CmdArgs args;
args << "GEORADIUS" << key << loc.first << loc.second;
detail::set_georadius_parameters(args,
radius,
unit,
count,
asc,
with_coord,
with_dist,
with_hash);
connection.send(args);
}
void georadiusbymember(Connection &connection,
const StringView &key,
const StringView &member,
double radius,
GeoUnit unit,
long long count,
bool asc,
bool with_coord,
bool with_dist,
bool with_hash) {
CmdArgs args;
args << "GEORADIUSBYMEMBER" << key << member;
detail::set_georadius_parameters(args,
radius,
unit,
count,
asc,
with_coord,
with_dist,
with_hash);
connection.send(args);
}
void georadiusbymember_store(Connection &connection,
const StringView &key,
const StringView &member,
double radius,
GeoUnit unit,
const StringView &destination,
bool store_dist,
long long count) {
CmdArgs args;
args << "GEORADIUSBYMEMBER" << key << member;
detail::set_georadius_store_parameters(args,
radius,
unit,
destination,
store_dist,
count);
connection.send(args);
}
// Stream commands.
void xtrim(Connection &connection, const StringView &key, long long count, bool approx) {
CmdArgs args;
args << "XTRIM" << key << "MAXLEN";
if (approx) {
args << "~";
}
args << count;
connection.send(args);
}
namespace detail {
void set_bitop(CmdArgs &args, BitOp op) {
args << "BITOP";
switch (op) {
case BitOp::AND:
args << "AND";
break;
case BitOp::OR:
args << "OR";
break;
case BitOp::XOR:
args << "XOR";
break;
case BitOp::NOT:
args << "NOT";
break;
default:
throw Error("Unknown bit operations");
}
}
void set_update_type(CmdArgs &args, UpdateType type) {
switch (type) {
case UpdateType::EXIST:
args << "XX";
break;
case UpdateType::NOT_EXIST:
args << "NX";
break;
case UpdateType::ALWAYS:
// Do nothing.
break;
default:
throw Error("Unknown update type");
}
}
void set_aggregation_type(CmdArgs &args, Aggregation aggr) {
args << "AGGREGATE";
switch (aggr) {
case Aggregation::SUM:
args << "SUM";
break;
case Aggregation::MIN:
args << "MIN";
break;
case Aggregation::MAX:
args << "MAX";
break;
default:
throw Error("Unknown aggregation type");
}
}
void set_geo_unit(CmdArgs &args, GeoUnit unit) {
switch (unit) {
case GeoUnit::M:
args << "m";
break;
case GeoUnit::KM:
args << "km";
break;
case GeoUnit::MI:
args << "mi";
break;
case GeoUnit::FT:
args << "ft";
break;
default:
throw Error("Unknown geo unit type");
break;
}
}
void set_georadius_store_parameters(CmdArgs &args,
double radius,
GeoUnit unit,
const StringView &destination,
bool store_dist,
long long count) {
args << radius;
detail::set_geo_unit(args, unit);
args << "COUNT" << count;
if (store_dist) {
args << "STOREDIST";
} else {
args << "STORE";
}
args << destination;
}
void set_georadius_parameters(CmdArgs &args,
double radius,
GeoUnit unit,
long long count,
bool asc,
bool with_coord,
bool with_dist,
bool with_hash) {
args << radius;
detail::set_geo_unit(args, unit);
if (with_coord) {
args << "WITHCOORD";
}
if (with_dist) {
args << "WITHDIST";
}
if (with_hash) {
args << "WITHHASH";
}
args << "COUNT" << count;
if (asc) {
args << "ASC";
} else {
args << "DESC";
}
}
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,180 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H
#define SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H
#include <vector>
#include <list>
#include <string>
#include <tuple>
#include "utils.h"
namespace sw {
namespace redis {
class CmdArgs {
public:
template <typename Arg>
CmdArgs& append(Arg &&arg);
template <typename Arg, typename ...Args>
CmdArgs& append(Arg &&arg, Args &&...args);
// All overloads of operator<< are for internal use only.
CmdArgs& operator<<(const StringView &arg);
template <typename T,
typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
int>::type = 0>
CmdArgs& operator<<(T &&arg);
template <typename Iter>
CmdArgs& operator<<(const std::pair<Iter, Iter> &range);
template <std::size_t N, typename ...Args>
auto operator<<(const std::tuple<Args...> &) ->
typename std::enable_if<N == sizeof...(Args), CmdArgs&>::type {
return *this;
}
template <std::size_t N = 0, typename ...Args>
auto operator<<(const std::tuple<Args...> &arg) ->
typename std::enable_if<N < sizeof...(Args), CmdArgs&>::type;
const char** argv() {
return _argv.data();
}
const std::size_t* argv_len() {
return _argv_len.data();
}
std::size_t size() const {
return _argv.size();
}
private:
// Deep copy.
CmdArgs& _append(std::string arg);
// Shallow copy.
CmdArgs& _append(const StringView &arg);
// Shallow copy.
CmdArgs& _append(const char *arg);
template <typename T,
typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
int>::type = 0>
CmdArgs& _append(T &&arg) {
return operator<<(std::forward<T>(arg));
}
template <typename Iter>
CmdArgs& _append(std::true_type, const std::pair<Iter, Iter> &range);
template <typename Iter>
CmdArgs& _append(std::false_type, const std::pair<Iter, Iter> &range);
std::vector<const char *> _argv;
std::vector<std::size_t> _argv_len;
std::list<std::string> _args;
};
template <typename Arg>
inline CmdArgs& CmdArgs::append(Arg &&arg) {
return _append(std::forward<Arg>(arg));
}
template <typename Arg, typename ...Args>
inline CmdArgs& CmdArgs::append(Arg &&arg, Args &&...args) {
_append(std::forward<Arg>(arg));
return append(std::forward<Args>(args)...);
}
inline CmdArgs& CmdArgs::operator<<(const StringView &arg) {
_argv.push_back(arg.data());
_argv_len.push_back(arg.size());
return *this;
}
template <typename Iter>
inline CmdArgs& CmdArgs::operator<<(const std::pair<Iter, Iter> &range) {
return _append(IsKvPair<typename std::decay<decltype(*std::declval<Iter>())>::type>(), range);
}
template <typename T,
typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
int>::type>
inline CmdArgs& CmdArgs::operator<<(T &&arg) {
return _append(std::to_string(std::forward<T>(arg)));
}
template <std::size_t N, typename ...Args>
auto CmdArgs::operator<<(const std::tuple<Args...> &arg) ->
typename std::enable_if<N < sizeof...(Args), CmdArgs&>::type {
operator<<(std::get<N>(arg));
return operator<<<N + 1, Args...>(arg);
}
inline CmdArgs& CmdArgs::_append(std::string arg) {
_args.push_back(std::move(arg));
return operator<<(_args.back());
}
inline CmdArgs& CmdArgs::_append(const StringView &arg) {
return operator<<(arg);
}
inline CmdArgs& CmdArgs::_append(const char *arg) {
return operator<<(arg);
}
template <typename Iter>
CmdArgs& CmdArgs::_append(std::false_type, const std::pair<Iter, Iter> &range) {
auto first = range.first;
auto last = range.second;
while (first != last) {
*this << *first;
++first;
}
return *this;
}
template <typename Iter>
CmdArgs& CmdArgs::_append(std::true_type, const std::pair<Iter, Iter> &range) {
auto first = range.first;
auto last = range.second;
while (first != last) {
*this << first->first << first->second;
++first;
}
return *this;
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H

View file

@ -0,0 +1,201 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "command_options.h"
#include "errors.h"
namespace {
const std::string NEGATIVE_INFINITY_NUMERIC = "-inf";
const std::string POSITIVE_INFINITY_NUMERIC = "+inf";
const std::string NEGATIVE_INFINITY_STRING = "-";
const std::string POSITIVE_INFINITY_STRING = "+";
std::string unbound(const std::string &bnd);
std::string bound(const std::string &bnd);
}
namespace sw {
namespace redis {
const std::string& UnboundedInterval<double>::min() const {
return NEGATIVE_INFINITY_NUMERIC;
}
const std::string& UnboundedInterval<double>::max() const {
return POSITIVE_INFINITY_NUMERIC;
}
BoundedInterval<double>::BoundedInterval(double min, double max, BoundType type) :
_min(std::to_string(min)),
_max(std::to_string(max)) {
switch (type) {
case BoundType::CLOSED:
// Do nothing
break;
case BoundType::OPEN:
_min = unbound(_min);
_max = unbound(_max);
break;
case BoundType::LEFT_OPEN:
_min = unbound(_min);
break;
case BoundType::RIGHT_OPEN:
_max = unbound(_max);
break;
default:
throw Error("Unknow BoundType");
}
}
LeftBoundedInterval<double>::LeftBoundedInterval(double min, BoundType type) :
_min(std::to_string(min)) {
switch (type) {
case BoundType::OPEN:
_min = unbound(_min);
break;
case BoundType::RIGHT_OPEN:
// Do nothing.
break;
default:
throw Error("Bound type can only be OPEN or RIGHT_OPEN");
}
}
const std::string& LeftBoundedInterval<double>::max() const {
return POSITIVE_INFINITY_NUMERIC;
}
RightBoundedInterval<double>::RightBoundedInterval(double max, BoundType type) :
_max(std::to_string(max)) {
switch (type) {
case BoundType::OPEN:
_max = unbound(_max);
break;
case BoundType::LEFT_OPEN:
// Do nothing.
break;
default:
throw Error("Bound type can only be OPEN or LEFT_OPEN");
}
}
const std::string& RightBoundedInterval<double>::min() const {
return NEGATIVE_INFINITY_NUMERIC;
}
const std::string& UnboundedInterval<std::string>::min() const {
return NEGATIVE_INFINITY_STRING;
}
const std::string& UnboundedInterval<std::string>::max() const {
return POSITIVE_INFINITY_STRING;
}
BoundedInterval<std::string>::BoundedInterval(const std::string &min,
const std::string &max,
BoundType type) {
switch (type) {
case BoundType::CLOSED:
_min = bound(min);
_max = bound(max);
break;
case BoundType::OPEN:
_min = unbound(min);
_max = unbound(max);
break;
case BoundType::LEFT_OPEN:
_min = unbound(min);
_max = bound(max);
break;
case BoundType::RIGHT_OPEN:
_min = bound(min);
_max = unbound(max);
break;
default:
throw Error("Unknow BoundType");
}
}
LeftBoundedInterval<std::string>::LeftBoundedInterval(const std::string &min, BoundType type) {
switch (type) {
case BoundType::OPEN:
_min = unbound(min);
break;
case BoundType::RIGHT_OPEN:
_min = bound(min);
break;
default:
throw Error("Bound type can only be OPEN or RIGHT_OPEN");
}
}
const std::string& LeftBoundedInterval<std::string>::max() const {
return POSITIVE_INFINITY_STRING;
}
RightBoundedInterval<std::string>::RightBoundedInterval(const std::string &max, BoundType type) {
switch (type) {
case BoundType::OPEN:
_max = unbound(max);
break;
case BoundType::LEFT_OPEN:
_max = bound(max);
break;
default:
throw Error("Bound type can only be OPEN or LEFT_OPEN");
}
}
const std::string& RightBoundedInterval<std::string>::min() const {
return NEGATIVE_INFINITY_STRING;
}
}
}
namespace {
std::string unbound(const std::string &bnd) {
return "(" + bnd;
}
std::string bound(const std::string &bnd) {
return "[" + bnd;
}
}

View file

@ -0,0 +1,211 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H
#define SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H
#include <string>
#include "utils.h"
namespace sw {
namespace redis {
enum class UpdateType {
EXIST,
NOT_EXIST,
ALWAYS
};
enum class InsertPosition {
BEFORE,
AFTER
};
enum class BoundType {
CLOSED,
OPEN,
LEFT_OPEN,
RIGHT_OPEN
};
// (-inf, +inf)
template <typename T>
class UnboundedInterval;
// [min, max], (min, max), (min, max], [min, max)
template <typename T>
class BoundedInterval;
// [min, +inf), (min, +inf)
template <typename T>
class LeftBoundedInterval;
// (-inf, max], (-inf, max)
template <typename T>
class RightBoundedInterval;
template <>
class UnboundedInterval<double> {
public:
const std::string& min() const;
const std::string& max() const;
};
template <>
class BoundedInterval<double> {
public:
BoundedInterval(double min, double max, BoundType type);
const std::string& min() const {
return _min;
}
const std::string& max() const {
return _max;
}
private:
std::string _min;
std::string _max;
};
template <>
class LeftBoundedInterval<double> {
public:
LeftBoundedInterval(double min, BoundType type);
const std::string& min() const {
return _min;
}
const std::string& max() const;
private:
std::string _min;
};
template <>
class RightBoundedInterval<double> {
public:
RightBoundedInterval(double max, BoundType type);
const std::string& min() const;
const std::string& max() const {
return _max;
}
private:
std::string _max;
};
template <>
class UnboundedInterval<std::string> {
public:
const std::string& min() const;
const std::string& max() const;
};
template <>
class BoundedInterval<std::string> {
public:
BoundedInterval(const std::string &min, const std::string &max, BoundType type);
const std::string& min() const {
return _min;
}
const std::string& max() const {
return _max;
}
private:
std::string _min;
std::string _max;
};
template <>
class LeftBoundedInterval<std::string> {
public:
LeftBoundedInterval(const std::string &min, BoundType type);
const std::string& min() const {
return _min;
}
const std::string& max() const;
private:
std::string _min;
};
template <>
class RightBoundedInterval<std::string> {
public:
RightBoundedInterval(const std::string &max, BoundType type);
const std::string& min() const;
const std::string& max() const {
return _max;
}
private:
std::string _max;
};
struct LimitOptions {
long long offset = 0;
long long count = -1;
};
enum class Aggregation {
SUM,
MIN,
MAX
};
enum class BitOp {
AND,
OR,
XOR,
NOT
};
enum class GeoUnit {
M,
KM,
MI,
FT
};
template <typename T>
struct WithCoord : TupleWithType<std::pair<double, double>, T> {};
template <typename T>
struct WithDist : TupleWithType<double, T> {};
template <typename T>
struct WithHash : TupleWithType<long long, T> {};
}
}
#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H

View file

@ -0,0 +1,305 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "connection.h"
#include <cassert>
#include "reply.h"
#include "command.h"
#include "command_args.h"
namespace sw {
namespace redis {
ConnectionOptions::ConnectionOptions(const std::string &uri) :
ConnectionOptions(_parse_options(uri)) {}
ConnectionOptions ConnectionOptions::_parse_options(const std::string &uri) const {
std::string type;
std::string path;
std::tie(type, path) = _split_string(uri, "://");
if (path.empty()) {
throw Error("Invalid URI: no path");
}
if (type == "tcp") {
return _parse_tcp_options(path);
} else if (type == "unix") {
return _parse_unix_options(path);
} else {
throw Error("Invalid URI: invalid type");
}
}
ConnectionOptions ConnectionOptions::_parse_tcp_options(const std::string &path) const {
ConnectionOptions options;
options.type = ConnectionType::TCP;
std::string host;
std::string port;
std::tie(host, port) = _split_string(path, ":");
options.host = host;
try {
if (!port.empty()) {
options.port = std::stoi(port);
} // else use default port, i.e. 6379.
} catch (const std::exception &) {
throw Error("Invalid URL: invalid port");
}
return options;
}
ConnectionOptions ConnectionOptions::_parse_unix_options(const std::string &path) const {
ConnectionOptions options;
options.type = ConnectionType::UNIX;
options.path = path;
return options;
}
auto ConnectionOptions::_split_string(const std::string &str, const std::string &delimiter) const ->
std::pair<std::string, std::string> {
auto pos = str.rfind(delimiter);
if (pos == std::string::npos) {
return {str, ""};
}
return {str.substr(0, pos), str.substr(pos + delimiter.size())};
}
class Connection::Connector {
public:
explicit Connector(const ConnectionOptions &opts);
ContextUPtr connect() const;
private:
ContextUPtr _connect() const;
redisContext* _connect_tcp() const;
redisContext* _connect_unix() const;
void _set_socket_timeout(redisContext &ctx) const;
void _enable_keep_alive(redisContext &ctx) const;
timeval _to_timeval(const std::chrono::milliseconds &dur) const;
const ConnectionOptions &_opts;
};
Connection::Connector::Connector(const ConnectionOptions &opts) : _opts(opts) {}
Connection::ContextUPtr Connection::Connector::connect() const {
auto ctx = _connect();
assert(ctx);
if (ctx->err != REDIS_OK) {
throw_error(*ctx, "Failed to connect to Redis");
}
_set_socket_timeout(*ctx);
_enable_keep_alive(*ctx);
return ctx;
}
Connection::ContextUPtr Connection::Connector::_connect() const {
redisContext *context = nullptr;
switch (_opts.type) {
case ConnectionType::TCP:
context = _connect_tcp();
break;
case ConnectionType::UNIX:
context = _connect_unix();
break;
default:
// Never goes here.
throw Error("Unkonw connection type");
}
if (context == nullptr) {
throw Error("Failed to allocate memory for connection.");
}
return ContextUPtr(context);
}
redisContext* Connection::Connector::_connect_tcp() const {
if (_opts.connect_timeout > std::chrono::milliseconds(0)) {
return redisConnectWithTimeout(_opts.host.c_str(),
_opts.port,
_to_timeval(_opts.connect_timeout));
} else {
return redisConnect(_opts.host.c_str(), _opts.port);
}
}
redisContext* Connection::Connector::_connect_unix() const {
if (_opts.connect_timeout > std::chrono::milliseconds(0)) {
return redisConnectUnixWithTimeout(
_opts.path.c_str(),
_to_timeval(_opts.connect_timeout));
} else {
return redisConnectUnix(_opts.path.c_str());
}
}
void Connection::Connector::_set_socket_timeout(redisContext &ctx) const {
if (_opts.socket_timeout <= std::chrono::milliseconds(0)) {
return;
}
if (redisSetTimeout(&ctx, _to_timeval(_opts.socket_timeout)) != REDIS_OK) {
throw_error(ctx, "Failed to set socket timeout");
}
}
void Connection::Connector::_enable_keep_alive(redisContext &ctx) const {
if (!_opts.keep_alive) {
return;
}
if (redisEnableKeepAlive(&ctx) != REDIS_OK) {
throw_error(ctx, "Failed to enable keep alive option");
}
}
timeval Connection::Connector::_to_timeval(const std::chrono::milliseconds &dur) const {
auto sec = std::chrono::duration_cast<std::chrono::seconds>(dur);
auto msec = std::chrono::duration_cast<std::chrono::microseconds>(dur - sec);
return {
static_cast<std::time_t>(sec.count()),
static_cast<suseconds_t>(msec.count())
};
}
void swap(Connection &lhs, Connection &rhs) noexcept {
std::swap(lhs._ctx, rhs._ctx);
std::swap(lhs._last_active, rhs._last_active);
std::swap(lhs._opts, rhs._opts);
}
Connection::Connection(const ConnectionOptions &opts) :
_ctx(Connector(opts).connect()),
_last_active(std::chrono::steady_clock::now()),
_opts(opts) {
assert(_ctx && !broken());
_set_options();
}
void Connection::reconnect() {
Connection connection(_opts);
swap(*this, connection);
}
void Connection::send(int argc, const char **argv, const std::size_t *argv_len) {
auto ctx = _context();
assert(ctx != nullptr);
if (redisAppendCommandArgv(ctx,
argc,
argv,
argv_len) != REDIS_OK) {
throw_error(*ctx, "Failed to send command");
}
assert(!broken());
}
void Connection::send(CmdArgs &args) {
auto ctx = _context();
assert(ctx != nullptr);
if (redisAppendCommandArgv(ctx,
args.size(),
args.argv(),
args.argv_len()) != REDIS_OK) {
throw_error(*ctx, "Failed to send command");
}
assert(!broken());
}
ReplyUPtr Connection::recv() {
auto *ctx = _context();
assert(ctx != nullptr);
void *r = nullptr;
if (redisGetReply(ctx, &r) != REDIS_OK) {
throw_error(*ctx, "Failed to get reply");
}
assert(!broken() && r != nullptr);
auto reply = ReplyUPtr(static_cast<redisReply*>(r));
if (reply::is_error(*reply)) {
throw_error(*reply);
}
return reply;
}
void Connection::_set_options() {
_auth();
_select_db();
}
void Connection::_auth() {
if (_opts.password.empty()) {
return;
}
cmd::auth(*this, _opts.password);
auto reply = recv();
reply::parse<void>(*reply);
}
void Connection::_select_db() {
if (_opts.db == 0) {
return;
}
cmd::select(*this, _opts.db);
auto reply = recv();
reply::parse<void>(*reply);
}
}
}

View file

@ -0,0 +1,194 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_H
#define SEWENEW_REDISPLUSPLUS_CONNECTION_H
#include <cerrno>
#include <cstring>
#include <memory>
#include <string>
#include <sstream>
#include <chrono>
#include <hiredis/hiredis.h>
#include "errors.h"
#include "reply.h"
#include "utils.h"
namespace sw {
namespace redis {
enum class ConnectionType {
TCP = 0,
UNIX
};
struct ConnectionOptions {
public:
ConnectionOptions() = default;
explicit ConnectionOptions(const std::string &uri);
ConnectionOptions(const ConnectionOptions &) = default;
ConnectionOptions& operator=(const ConnectionOptions &) = default;
ConnectionOptions(ConnectionOptions &&) = default;
ConnectionOptions& operator=(ConnectionOptions &&) = default;
~ConnectionOptions() = default;
ConnectionType type = ConnectionType::TCP;
std::string host;
int port = 6379;
std::string path;
std::string password;
int db = 0;
bool keep_alive = false;
std::chrono::milliseconds connect_timeout{0};
std::chrono::milliseconds socket_timeout{0};
private:
ConnectionOptions _parse_options(const std::string &uri) const;
ConnectionOptions _parse_tcp_options(const std::string &path) const;
ConnectionOptions _parse_unix_options(const std::string &path) const;
auto _split_string(const std::string &str, const std::string &delimiter) const ->
std::pair<std::string, std::string>;
};
class CmdArgs;
class Connection {
public:
explicit Connection(const ConnectionOptions &opts);
Connection(const Connection &) = delete;
Connection& operator=(const Connection &) = delete;
Connection(Connection &&) = default;
Connection& operator=(Connection &&) = default;
~Connection() = default;
// Check if the connection is broken. Client needs to do this check
// before sending some command to the connection. If it's broken,
// client needs to reconnect it.
bool broken() const noexcept {
return _ctx->err != REDIS_OK;
}
void reset() noexcept {
_ctx->err = 0;
}
void reconnect();
auto last_active() const
-> std::chrono::time_point<std::chrono::steady_clock> {
return _last_active;
}
template <typename ...Args>
void send(const char *format, Args &&...args);
void send(int argc, const char **argv, const std::size_t *argv_len);
void send(CmdArgs &args);
ReplyUPtr recv();
const ConnectionOptions& options() const {
return _opts;
}
friend void swap(Connection &lhs, Connection &rhs) noexcept;
private:
class Connector;
struct ContextDeleter {
void operator()(redisContext *context) const {
if (context != nullptr) {
redisFree(context);
}
};
};
using ContextUPtr = std::unique_ptr<redisContext, ContextDeleter>;
void _set_options();
void _auth();
void _select_db();
redisContext* _context();
ContextUPtr _ctx;
// The time that the connection is created or the time that
// the connection is used, i.e. *context()* is called.
std::chrono::time_point<std::chrono::steady_clock> _last_active{};
ConnectionOptions _opts;
};
using ConnectionSPtr = std::shared_ptr<Connection>;
enum class Role {
MASTER,
SLAVE
};
// Inline implementaions.
template <typename ...Args>
inline void Connection::send(const char *format, Args &&...args) {
auto ctx = _context();
assert(ctx != nullptr);
if (redisAppendCommand(ctx,
format,
std::forward<Args>(args)...) != REDIS_OK) {
throw_error(*ctx, "Failed to send command");
}
assert(!broken());
}
inline redisContext* Connection::_context() {
_last_active = std::chrono::steady_clock::now();
return _ctx.get();
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_H

View file

@ -0,0 +1,249 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "connection_pool.h"
#include <cassert>
#include "errors.h"
namespace sw {
namespace redis {
ConnectionPool::ConnectionPool(const ConnectionPoolOptions &pool_opts,
const ConnectionOptions &connection_opts) :
_opts(connection_opts),
_pool_opts(pool_opts) {
if (_pool_opts.size == 0) {
throw Error("CANNOT create an empty pool");
}
// Lazily create connections.
}
ConnectionPool::ConnectionPool(SimpleSentinel sentinel,
const ConnectionPoolOptions &pool_opts,
const ConnectionOptions &connection_opts) :
_opts(connection_opts),
_pool_opts(pool_opts),
_sentinel(std::move(sentinel)) {
// In this case, the connection must be of TCP type.
if (_opts.type != ConnectionType::TCP) {
throw Error("Sentinel only supports TCP connection");
}
if (_opts.connect_timeout == std::chrono::milliseconds(0)
|| _opts.socket_timeout == std::chrono::milliseconds(0)) {
throw Error("With sentinel, connection timeout and socket timeout cannot be 0");
}
// Cleanup connection options.
_update_connection_opts("", -1);
assert(_sentinel);
}
ConnectionPool::ConnectionPool(ConnectionPool &&that) {
std::lock_guard<std::mutex> lock(that._mutex);
_move(std::move(that));
}
ConnectionPool& ConnectionPool::operator=(ConnectionPool &&that) {
if (this != &that) {
std::lock(_mutex, that._mutex);
std::lock_guard<std::mutex> lock_this(_mutex, std::adopt_lock);
std::lock_guard<std::mutex> lock_that(that._mutex, std::adopt_lock);
_move(std::move(that));
}
return *this;
}
Connection ConnectionPool::fetch() {
std::unique_lock<std::mutex> lock(_mutex);
if (_pool.empty()) {
if (_used_connections == _pool_opts.size) {
_wait_for_connection(lock);
} else {
// Lazily create a new connection.
auto connection = _create();
++_used_connections;
return connection;
}
}
// _pool is NOT empty.
auto connection = _fetch();
auto connection_lifetime = _pool_opts.connection_lifetime;
if (_sentinel) {
auto opts = _opts;
auto role_changed = _role_changed(connection.options());
auto sentinel = _sentinel;
lock.unlock();
if (role_changed || _need_reconnect(connection, connection_lifetime)) {
try {
connection = _create(sentinel, opts, false);
} catch (const Error &e) {
// Failed to reconnect, return it to the pool, and retry latter.
release(std::move(connection));
throw;
}
}
return connection;
}
lock.unlock();
if (_need_reconnect(connection, connection_lifetime)) {
try {
connection.reconnect();
} catch (const Error &e) {
// Failed to reconnect, return it to the pool, and retry latter.
release(std::move(connection));
throw;
}
}
return connection;
}
ConnectionOptions ConnectionPool::connection_options() {
std::lock_guard<std::mutex> lock(_mutex);
return _opts;
}
void ConnectionPool::release(Connection connection) {
{
std::lock_guard<std::mutex> lock(_mutex);
_pool.push_back(std::move(connection));
}
_cv.notify_one();
}
Connection ConnectionPool::create() {
std::unique_lock<std::mutex> lock(_mutex);
auto opts = _opts;
if (_sentinel) {
auto sentinel = _sentinel;
lock.unlock();
return _create(sentinel, opts, false);
} else {
lock.unlock();
return Connection(opts);
}
}
void ConnectionPool::_move(ConnectionPool &&that) {
_opts = std::move(that._opts);
_pool_opts = std::move(that._pool_opts);
_pool = std::move(that._pool);
_used_connections = that._used_connections;
_sentinel = std::move(that._sentinel);
}
Connection ConnectionPool::_create() {
if (_sentinel) {
// Get Redis host and port info from sentinel.
return _create(_sentinel, _opts, true);
}
return Connection(_opts);
}
Connection ConnectionPool::_create(SimpleSentinel &sentinel,
const ConnectionOptions &opts,
bool locked) {
try {
auto connection = sentinel.create(opts);
std::unique_lock<std::mutex> lock(_mutex, std::defer_lock);
if (!locked) {
lock.lock();
}
const auto &connection_opts = connection.options();
if (_role_changed(connection_opts)) {
// Master/Slave has been changed, reconnect all connections.
_update_connection_opts(connection_opts.host, connection_opts.port);
}
return connection;
} catch (const StopIterError &e) {
throw Error("Failed to create connection with sentinel");
}
}
Connection ConnectionPool::_fetch() {
assert(!_pool.empty());
auto connection = std::move(_pool.front());
_pool.pop_front();
return connection;
}
void ConnectionPool::_wait_for_connection(std::unique_lock<std::mutex> &lock) {
auto timeout = _pool_opts.wait_timeout;
if (timeout > std::chrono::milliseconds(0)) {
// Wait until _pool is no longer empty or timeout.
if (!_cv.wait_for(lock,
timeout,
[this] { return !(this->_pool).empty(); })) {
throw Error("Failed to fetch a connection in "
+ std::to_string(timeout.count()) + " milliseconds");
}
} else {
// Wait forever.
_cv.wait(lock, [this] { return !(this->_pool).empty(); });
}
}
bool ConnectionPool::_need_reconnect(const Connection &connection,
const std::chrono::milliseconds &connection_lifetime) const {
if (connection.broken()) {
return true;
}
if (connection_lifetime > std::chrono::milliseconds(0)) {
auto now = std::chrono::steady_clock::now();
if (now - connection.last_active() > connection_lifetime) {
return true;
}
}
return false;
}
}
}

View file

@ -0,0 +1,115 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H
#define SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H
#include <chrono>
#include <mutex>
#include <memory>
#include <condition_variable>
#include <deque>
#include "connection.h"
#include "sentinel.h"
namespace sw {
namespace redis {
struct ConnectionPoolOptions {
// Max number of connections, including both in-use and idle ones.
std::size_t size = 1;
// Max time to wait for a connection. 0ms means client waits forever.
std::chrono::milliseconds wait_timeout{0};
// Max lifetime of a connection. 0ms means we never expire the connection.
std::chrono::milliseconds connection_lifetime{0};
};
class ConnectionPool {
public:
ConnectionPool(const ConnectionPoolOptions &pool_opts,
const ConnectionOptions &connection_opts);
ConnectionPool(SimpleSentinel sentinel,
const ConnectionPoolOptions &pool_opts,
const ConnectionOptions &connection_opts);
ConnectionPool() = default;
ConnectionPool(ConnectionPool &&that);
ConnectionPool& operator=(ConnectionPool &&that);
ConnectionPool(const ConnectionPool &) = delete;
ConnectionPool& operator=(const ConnectionPool &) = delete;
~ConnectionPool() = default;
// Fetch a connection from pool.
Connection fetch();
ConnectionOptions connection_options();
void release(Connection connection);
// Create a new connection.
Connection create();
private:
void _move(ConnectionPool &&that);
// NOT thread-safe
Connection _create();
Connection _create(SimpleSentinel &sentinel, const ConnectionOptions &opts, bool locked);
Connection _fetch();
void _wait_for_connection(std::unique_lock<std::mutex> &lock);
bool _need_reconnect(const Connection &connection,
const std::chrono::milliseconds &connection_lifetime) const;
void _update_connection_opts(const std::string &host, int port) {
_opts.host = host;
_opts.port = port;
}
bool _role_changed(const ConnectionOptions &opts) const {
return opts.port != _opts.port || opts.host != _opts.host;
}
ConnectionOptions _opts;
ConnectionPoolOptions _pool_opts;
std::deque<Connection> _pool;
std::size_t _used_connections = 0;
std::mutex _mutex;
std::condition_variable _cv;
SimpleSentinel _sentinel;
};
}
}
#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H

View file

@ -0,0 +1,96 @@
/*
* Copyright 2001-2010 Georges Menie (www.menie.org)
* Copyright 2010-2012 Salvatore Sanfilippo (adapted to Redis coding style)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the University of California, Berkeley nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* CRC16 implementation according to CCITT standards.
*
* Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the
* following parameters:
*
* Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN"
* Width : 16 bit
* Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1)
* Initialization : 0000
* Reflect Input byte : False
* Reflect Output CRC : False
* Xor constant to output CRC : 0000
* Output for "123456789" : 31C3
*/
#include <cstdint>
namespace sw {
namespace redis {
static const uint16_t crc16tab[256]= {
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
};
uint16_t crc16(const char *buf, int len) {
int counter;
uint16_t crc = 0;
for (counter = 0; counter < len; counter++)
crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF];
return crc;
}
}
}

View file

@ -0,0 +1,136 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "errors.h"
#include <cassert>
#include <cerrno>
#include <unordered_map>
#include <tuple>
#include "shards.h"
namespace {
using namespace sw::redis;
std::pair<ReplyErrorType, std::string> parse_error(const std::string &msg);
std::unordered_map<std::string, ReplyErrorType> error_map = {
{"MOVED", ReplyErrorType::MOVED},
{"ASK", ReplyErrorType::ASK}
};
}
namespace sw {
namespace redis {
void throw_error(redisContext &context, const std::string &err_info) {
auto err_code = context.err;
const auto *err_str = context.errstr;
if (err_str == nullptr) {
throw Error(err_info + ": null error message: " + std::to_string(err_code));
}
auto err_msg = err_info + ": " + err_str;
switch (err_code) {
case REDIS_ERR_IO:
if (errno == EAGAIN || errno == EINTR) {
throw TimeoutError(err_msg);
} else {
throw IoError(err_msg);
}
break;
case REDIS_ERR_EOF:
throw ClosedError(err_msg);
break;
case REDIS_ERR_PROTOCOL:
throw ProtoError(err_msg);
break;
case REDIS_ERR_OOM:
throw OomError(err_msg);
break;
case REDIS_ERR_OTHER:
throw Error(err_msg);
break;
default:
throw Error(err_info + ": Unknown error code");
}
}
void throw_error(const redisReply &reply) {
assert(reply.type == REDIS_REPLY_ERROR);
if (reply.str == nullptr) {
throw Error("Null error reply");
}
auto err_str = std::string(reply.str, reply.len);
auto err_type = ReplyErrorType::ERR;
std::string err_msg;
std::tie(err_type, err_msg) = parse_error(err_str);
switch (err_type) {
case ReplyErrorType::MOVED:
throw MovedError(err_msg);
break;
case ReplyErrorType::ASK:
throw AskError(err_msg);
break;
default:
throw ReplyError(err_str);
break;
}
}
}
}
namespace {
using namespace sw::redis;
std::pair<ReplyErrorType, std::string> parse_error(const std::string &err) {
// The error contains an Error Prefix, and an optional error message.
auto idx = err.find_first_of(" \n");
if (idx == std::string::npos) {
throw ProtoError("No Error Prefix: " + err);
}
auto err_prefix = err.substr(0, idx);
auto err_type = ReplyErrorType::ERR;
auto iter = error_map.find(err_prefix);
if (iter != error_map.end()) {
// Specific error.
err_type = iter->second;
} // else Generic error.
return {err_type, err.substr(idx + 1)};
}
}

View file

@ -0,0 +1,159 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_ERRORS_H
#define SEWENEW_REDISPLUSPLUS_ERRORS_H
#include <exception>
#include <string>
#include <hiredis/hiredis.h>
namespace sw {
namespace redis {
enum ReplyErrorType {
ERR,
MOVED,
ASK
};
class Error : public std::exception {
public:
explicit Error(const std::string &msg) : _msg(msg) {}
Error(const Error &) = default;
Error& operator=(const Error &) = default;
Error(Error &&) = default;
Error& operator=(Error &&) = default;
virtual ~Error() = default;
virtual const char* what() const noexcept {
return _msg.data();
}
private:
std::string _msg;
};
class IoError : public Error {
public:
explicit IoError(const std::string &msg) : Error(msg) {}
IoError(const IoError &) = default;
IoError& operator=(const IoError &) = default;
IoError(IoError &&) = default;
IoError& operator=(IoError &&) = default;
virtual ~IoError() = default;
};
class TimeoutError : public IoError {
public:
explicit TimeoutError(const std::string &msg) : IoError(msg) {}
TimeoutError(const TimeoutError &) = default;
TimeoutError& operator=(const TimeoutError &) = default;
TimeoutError(TimeoutError &&) = default;
TimeoutError& operator=(TimeoutError &&) = default;
virtual ~TimeoutError() = default;
};
class ClosedError : public Error {
public:
explicit ClosedError(const std::string &msg) : Error(msg) {}
ClosedError(const ClosedError &) = default;
ClosedError& operator=(const ClosedError &) = default;
ClosedError(ClosedError &&) = default;
ClosedError& operator=(ClosedError &&) = default;
virtual ~ClosedError() = default;
};
class ProtoError : public Error {
public:
explicit ProtoError(const std::string &msg) : Error(msg) {}
ProtoError(const ProtoError &) = default;
ProtoError& operator=(const ProtoError &) = default;
ProtoError(ProtoError &&) = default;
ProtoError& operator=(ProtoError &&) = default;
virtual ~ProtoError() = default;
};
class OomError : public Error {
public:
explicit OomError(const std::string &msg) : Error(msg) {}
OomError(const OomError &) = default;
OomError& operator=(const OomError &) = default;
OomError(OomError &&) = default;
OomError& operator=(OomError &&) = default;
virtual ~OomError() = default;
};
class ReplyError : public Error {
public:
explicit ReplyError(const std::string &msg) : Error(msg) {}
ReplyError(const ReplyError &) = default;
ReplyError& operator=(const ReplyError &) = default;
ReplyError(ReplyError &&) = default;
ReplyError& operator=(ReplyError &&) = default;
virtual ~ReplyError() = default;
};
class WatchError : public Error {
public:
explicit WatchError() : Error("Watched key has been modified") {}
WatchError(const WatchError &) = default;
WatchError& operator=(const WatchError &) = default;
WatchError(WatchError &&) = default;
WatchError& operator=(WatchError &&) = default;
virtual ~WatchError() = default;
};
// MovedError and AskError are defined in shards.h
class MovedError;
class AskError;
void throw_error(redisContext &context, const std::string &err_info);
void throw_error(const redisReply &reply);
}
}
#endif // end SEWENEW_REDISPLUSPLUS_ERRORS_H

View file

@ -0,0 +1,35 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "pipeline.h"
namespace sw {
namespace redis {
std::vector<ReplyUPtr> PipelineImpl::exec(Connection &connection, std::size_t cmd_num) {
std::vector<ReplyUPtr> replies;
while (cmd_num > 0) {
replies.push_back(connection.recv());
--cmd_num;
}
return replies;
}
}
}

View file

@ -0,0 +1,49 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_PIPELINE_H
#define SEWENEW_REDISPLUSPLUS_PIPELINE_H
#include <cassert>
#include <vector>
#include "connection.h"
namespace sw {
namespace redis {
class PipelineImpl {
public:
template <typename Cmd, typename ...Args>
void command(Connection &connection, Cmd cmd, Args &&...args) {
assert(!connection.broken());
cmd(connection, std::forward<Args>(args)...);
}
std::vector<ReplyUPtr> exec(Connection &connection, std::size_t cmd_num);
void discard(Connection &connection, std::size_t /*cmd_num*/) {
// Reconnect to Redis to discard all commands.
connection.reconnect();
}
};
}
}
#endif // end SEWENEW_REDISPLUSPLUS_PIPELINE_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,208 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP
#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP
namespace sw {
namespace redis {
template <typename Impl>
template <typename ...Args>
QueuedRedis<Impl>::QueuedRedis(const ConnectionSPtr &connection, Args &&...args) :
_connection(connection),
_impl(std::forward<Args>(args)...) {
assert(_connection);
}
template <typename Impl>
Redis QueuedRedis<Impl>::redis() {
return Redis(_connection);
}
template <typename Impl>
template <typename Cmd, typename ...Args>
auto QueuedRedis<Impl>::command(Cmd cmd, Args &&...args)
-> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value,
QueuedRedis<Impl>&>::type {
try {
_sanity_check();
_impl.command(*_connection, cmd, std::forward<Args>(args)...);
++_cmd_num;
} catch (const Error &e) {
_invalidate();
throw;
}
return *this;
}
template <typename Impl>
template <typename ...Args>
QueuedRedis<Impl>& QueuedRedis<Impl>::command(const StringView &cmd_name, Args &&...args) {
auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) {
CmdArgs cmd_args;
cmd_args.append(cmd_name, std::forward<Args>(args)...);
connection.send(cmd_args);
};
return command(cmd, cmd_name, std::forward<Args>(args)...);
}
template <typename Impl>
template <typename Input>
auto QueuedRedis<Impl>::command(Input first, Input last)
-> typename std::enable_if<IsIter<Input>::value, QueuedRedis<Impl>&>::type {
if (first == last) {
throw Error("command: empty range");
}
auto cmd = [](Connection &connection, Input first, Input last) {
CmdArgs cmd_args;
while (first != last) {
cmd_args.append(*first);
++first;
}
connection.send(cmd_args);
};
return command(cmd, first, last);
}
template <typename Impl>
QueuedReplies QueuedRedis<Impl>::exec() {
try {
_sanity_check();
auto replies = _impl.exec(*_connection, _cmd_num);
_rewrite_replies(replies);
_reset();
return QueuedReplies(std::move(replies));
} catch (const Error &e) {
_invalidate();
throw;
}
}
template <typename Impl>
void QueuedRedis<Impl>::discard() {
try {
_sanity_check();
_impl.discard(*_connection, _cmd_num);
_reset();
} catch (const Error &e) {
_invalidate();
throw;
}
}
template <typename Impl>
void QueuedRedis<Impl>::_sanity_check() const {
if (!_valid) {
throw Error("Not in valid state");
}
if (_connection->broken()) {
throw Error("Connection is broken");
}
}
template <typename Impl>
inline void QueuedRedis<Impl>::_reset() {
_cmd_num = 0;
_set_cmd_indexes.clear();
_georadius_cmd_indexes.clear();
}
template <typename Impl>
void QueuedRedis<Impl>::_invalidate() {
_valid = false;
_reset();
}
template <typename Impl>
void QueuedRedis<Impl>::_rewrite_replies(std::vector<ReplyUPtr> &replies) const {
_rewrite_replies(_set_cmd_indexes, reply::rewrite_set_reply, replies);
_rewrite_replies(_georadius_cmd_indexes, reply::rewrite_georadius_reply, replies);
}
template <typename Impl>
template <typename Func>
void QueuedRedis<Impl>::_rewrite_replies(const std::vector<std::size_t> &indexes,
Func rewriter,
std::vector<ReplyUPtr> &replies) const {
for (auto idx : indexes) {
assert(idx < replies.size());
auto &reply = replies[idx];
assert(reply);
rewriter(*reply);
}
}
inline std::size_t QueuedReplies::size() const {
return _replies.size();
}
inline redisReply& QueuedReplies::get(std::size_t idx) {
_index_check(idx);
auto &reply = _replies[idx];
assert(reply);
return *reply;
}
template <typename Result>
inline Result QueuedReplies::get(std::size_t idx) {
auto &reply = get(idx);
return reply::parse<Result>(reply);
}
template <typename Output>
inline void QueuedReplies::get(std::size_t idx, Output output) {
auto &reply = get(idx);
reply::to_array(reply, output);
}
inline void QueuedReplies::_index_check(std::size_t idx) const {
if (idx >= size()) {
throw Error("Out of range");
}
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP

View file

@ -0,0 +1,25 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H
#define SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H
#include "redis.h"
#include "redis_cluster.h"
#include "queued_redis.h"
#include "sentinel.h"
#endif // end SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H

View file

@ -0,0 +1,882 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "redis.h"
#include <hiredis/hiredis.h>
#include "command.h"
#include "errors.h"
#include "queued_redis.h"
namespace sw {
namespace redis {
Redis::Redis(const std::string &uri) : Redis(ConnectionOptions(uri)) {}
Redis::Redis(const ConnectionSPtr &connection) : _connection(connection) {
assert(_connection);
}
Pipeline Redis::pipeline() {
return Pipeline(std::make_shared<Connection>(_pool.create()));
}
Transaction Redis::transaction(bool piped) {
return Transaction(std::make_shared<Connection>(_pool.create()), piped);
}
Subscriber Redis::subscriber() {
return Subscriber(_pool.create());
}
// CONNECTION commands.
void Redis::auth(const StringView &password) {
auto reply = command(cmd::auth, password);
reply::parse<void>(*reply);
}
std::string Redis::echo(const StringView &msg) {
auto reply = command(cmd::echo, msg);
return reply::parse<std::string>(*reply);
}
std::string Redis::ping() {
auto reply = command<void (*)(Connection &)>(cmd::ping);
return reply::to_status(*reply);
}
std::string Redis::ping(const StringView &msg) {
auto reply = command<void (*)(Connection &, const StringView &)>(cmd::ping, msg);
return reply::parse<std::string>(*reply);
}
void Redis::swapdb(long long idx1, long long idx2) {
auto reply = command(cmd::swapdb, idx1, idx2);
reply::parse<void>(*reply);
}
// SERVER commands.
void Redis::bgrewriteaof() {
auto reply = command(cmd::bgrewriteaof);
reply::parse<void>(*reply);
}
void Redis::bgsave() {
auto reply = command(cmd::bgsave);
reply::parse<void>(*reply);
}
long long Redis::dbsize() {
auto reply = command(cmd::dbsize);
return reply::parse<long long>(*reply);
}
void Redis::flushall(bool async) {
auto reply = command(cmd::flushall, async);
reply::parse<void>(*reply);
}
void Redis::flushdb(bool async) {
auto reply = command(cmd::flushdb, async);
reply::parse<void>(*reply);
}
std::string Redis::info() {
auto reply = command<void (*)(Connection &)>(cmd::info);
return reply::parse<std::string>(*reply);
}
std::string Redis::info(const StringView &section) {
auto reply = command<void (*)(Connection &, const StringView &)>(cmd::info, section);
return reply::parse<std::string>(*reply);
}
long long Redis::lastsave() {
auto reply = command(cmd::lastsave);
return reply::parse<long long>(*reply);
}
void Redis::save() {
auto reply = command(cmd::save);
reply::parse<void>(*reply);
}
// KEY commands.
long long Redis::del(const StringView &key) {
auto reply = command(cmd::del, key);
return reply::parse<long long>(*reply);
}
OptionalString Redis::dump(const StringView &key) {
auto reply = command(cmd::dump, key);
return reply::parse<OptionalString>(*reply);
}
long long Redis::exists(const StringView &key) {
auto reply = command(cmd::exists, key);
return reply::parse<long long>(*reply);
}
bool Redis::expire(const StringView &key, long long timeout) {
auto reply = command(cmd::expire, key, timeout);
return reply::parse<bool>(*reply);
}
bool Redis::expireat(const StringView &key, long long timestamp) {
auto reply = command(cmd::expireat, key, timestamp);
return reply::parse<bool>(*reply);
}
bool Redis::move(const StringView &key, long long db) {
auto reply = command(cmd::move, key, db);
return reply::parse<bool>(*reply);
}
bool Redis::persist(const StringView &key) {
auto reply = command(cmd::persist, key);
return reply::parse<bool>(*reply);
}
bool Redis::pexpire(const StringView &key, long long timeout) {
auto reply = command(cmd::pexpire, key, timeout);
return reply::parse<bool>(*reply);
}
bool Redis::pexpireat(const StringView &key, long long timestamp) {
auto reply = command(cmd::pexpireat, key, timestamp);
return reply::parse<bool>(*reply);
}
long long Redis::pttl(const StringView &key) {
auto reply = command(cmd::pttl, key);
return reply::parse<long long>(*reply);
}
OptionalString Redis::randomkey() {
auto reply = command(cmd::randomkey);
return reply::parse<OptionalString>(*reply);
}
void Redis::rename(const StringView &key, const StringView &newkey) {
auto reply = command(cmd::rename, key, newkey);
reply::parse<void>(*reply);
}
bool Redis::renamenx(const StringView &key, const StringView &newkey) {
auto reply = command(cmd::renamenx, key, newkey);
return reply::parse<bool>(*reply);
}
void Redis::restore(const StringView &key,
const StringView &val,
long long ttl,
bool replace) {
auto reply = command(cmd::restore, key, val, ttl, replace);
reply::parse<void>(*reply);
}
long long Redis::touch(const StringView &key) {
auto reply = command(cmd::touch, key);
return reply::parse<long long>(*reply);
}
long long Redis::ttl(const StringView &key) {
auto reply = command(cmd::ttl, key);
return reply::parse<long long>(*reply);
}
std::string Redis::type(const StringView &key) {
auto reply = command(cmd::type, key);
return reply::parse<std::string>(*reply);
}
long long Redis::unlink(const StringView &key) {
auto reply = command(cmd::unlink, key);
return reply::parse<long long>(*reply);
}
long long Redis::wait(long long numslaves, long long timeout) {
auto reply = command(cmd::wait, numslaves, timeout);
return reply::parse<long long>(*reply);
}
// STRING commands.
long long Redis::append(const StringView &key, const StringView &val) {
auto reply = command(cmd::append, key, val);
return reply::parse<long long>(*reply);
}
long long Redis::bitcount(const StringView &key, long long start, long long end) {
auto reply = command(cmd::bitcount, key, start, end);
return reply::parse<long long>(*reply);
}
long long Redis::bitop(BitOp op, const StringView &destination, const StringView &key) {
auto reply = command(cmd::bitop, op, destination, key);
return reply::parse<long long>(*reply);
}
long long Redis::bitpos(const StringView &key,
long long bit,
long long start,
long long end) {
auto reply = command(cmd::bitpos, key, bit, start, end);
return reply::parse<long long>(*reply);
}
long long Redis::decr(const StringView &key) {
auto reply = command(cmd::decr, key);
return reply::parse<long long>(*reply);
}
long long Redis::decrby(const StringView &key, long long decrement) {
auto reply = command(cmd::decrby, key, decrement);
return reply::parse<long long>(*reply);
}
OptionalString Redis::get(const StringView &key) {
auto reply = command(cmd::get, key);
return reply::parse<OptionalString>(*reply);
}
long long Redis::getbit(const StringView &key, long long offset) {
auto reply = command(cmd::getbit, key, offset);
return reply::parse<long long>(*reply);
}
std::string Redis::getrange(const StringView &key, long long start, long long end) {
auto reply = command(cmd::getrange, key, start, end);
return reply::parse<std::string>(*reply);
}
OptionalString Redis::getset(const StringView &key, const StringView &val) {
auto reply = command(cmd::getset, key, val);
return reply::parse<OptionalString>(*reply);
}
long long Redis::incr(const StringView &key) {
auto reply = command(cmd::incr, key);
return reply::parse<long long>(*reply);
}
long long Redis::incrby(const StringView &key, long long increment) {
auto reply = command(cmd::incrby, key, increment);
return reply::parse<long long>(*reply);
}
double Redis::incrbyfloat(const StringView &key, double increment) {
auto reply = command(cmd::incrbyfloat, key, increment);
return reply::parse<double>(*reply);
}
void Redis::psetex(const StringView &key,
long long ttl,
const StringView &val) {
auto reply = command(cmd::psetex, key, ttl, val);
reply::parse<void>(*reply);
}
bool Redis::set(const StringView &key,
const StringView &val,
const std::chrono::milliseconds &ttl,
UpdateType type) {
auto reply = command(cmd::set, key, val, ttl.count(), type);
reply::rewrite_set_reply(*reply);
return reply::parse<bool>(*reply);
}
void Redis::setex(const StringView &key,
long long ttl,
const StringView &val) {
auto reply = command(cmd::setex, key, ttl, val);
reply::parse<void>(*reply);
}
bool Redis::setnx(const StringView &key, const StringView &val) {
auto reply = command(cmd::setnx, key, val);
return reply::parse<bool>(*reply);
}
long long Redis::setrange(const StringView &key, long long offset, const StringView &val) {
auto reply = command(cmd::setrange, key, offset, val);
return reply::parse<long long>(*reply);
}
long long Redis::strlen(const StringView &key) {
auto reply = command(cmd::strlen, key);
return reply::parse<long long>(*reply);
}
// LIST commands.
OptionalStringPair Redis::blpop(const StringView &key, long long timeout) {
auto reply = command(cmd::blpop, key, timeout);
return reply::parse<OptionalStringPair>(*reply);
}
OptionalStringPair Redis::blpop(const StringView &key, const std::chrono::seconds &timeout) {
return blpop(key, timeout.count());
}
OptionalStringPair Redis::brpop(const StringView &key, long long timeout) {
auto reply = command(cmd::brpop, key, timeout);
return reply::parse<OptionalStringPair>(*reply);
}
OptionalStringPair Redis::brpop(const StringView &key, const std::chrono::seconds &timeout) {
return brpop(key, timeout.count());
}
OptionalString Redis::brpoplpush(const StringView &source,
const StringView &destination,
long long timeout) {
auto reply = command(cmd::brpoplpush, source, destination, timeout);
return reply::parse<OptionalString>(*reply);
}
OptionalString Redis::lindex(const StringView &key, long long index) {
auto reply = command(cmd::lindex, key, index);
return reply::parse<OptionalString>(*reply);
}
long long Redis::linsert(const StringView &key,
InsertPosition position,
const StringView &pivot,
const StringView &val) {
auto reply = command(cmd::linsert, key, position, pivot, val);
return reply::parse<long long>(*reply);
}
long long Redis::llen(const StringView &key) {
auto reply = command(cmd::llen, key);
return reply::parse<long long>(*reply);
}
OptionalString Redis::lpop(const StringView &key) {
auto reply = command(cmd::lpop, key);
return reply::parse<OptionalString>(*reply);
}
long long Redis::lpush(const StringView &key, const StringView &val) {
auto reply = command(cmd::lpush, key, val);
return reply::parse<long long>(*reply);
}
long long Redis::lpushx(const StringView &key, const StringView &val) {
auto reply = command(cmd::lpushx, key, val);
return reply::parse<long long>(*reply);
}
long long Redis::lrem(const StringView &key, long long count, const StringView &val) {
auto reply = command(cmd::lrem, key, count, val);
return reply::parse<long long>(*reply);
}
void Redis::lset(const StringView &key, long long index, const StringView &val) {
auto reply = command(cmd::lset, key, index, val);
reply::parse<void>(*reply);
}
void Redis::ltrim(const StringView &key, long long start, long long stop) {
auto reply = command(cmd::ltrim, key, start, stop);
reply::parse<void>(*reply);
}
OptionalString Redis::rpop(const StringView &key) {
auto reply = command(cmd::rpop, key);
return reply::parse<OptionalString>(*reply);
}
OptionalString Redis::rpoplpush(const StringView &source, const StringView &destination) {
auto reply = command(cmd::rpoplpush, source, destination);
return reply::parse<OptionalString>(*reply);
}
long long Redis::rpush(const StringView &key, const StringView &val) {
auto reply = command(cmd::rpush, key, val);
return reply::parse<long long>(*reply);
}
long long Redis::rpushx(const StringView &key, const StringView &val) {
auto reply = command(cmd::rpushx, key, val);
return reply::parse<long long>(*reply);
}
long long Redis::hdel(const StringView &key, const StringView &field) {
auto reply = command(cmd::hdel, key, field);
return reply::parse<long long>(*reply);
}
bool Redis::hexists(const StringView &key, const StringView &field) {
auto reply = command(cmd::hexists, key, field);
return reply::parse<bool>(*reply);
}
OptionalString Redis::hget(const StringView &key, const StringView &field) {
auto reply = command(cmd::hget, key, field);
return reply::parse<OptionalString>(*reply);
}
long long Redis::hincrby(const StringView &key, const StringView &field, long long increment) {
auto reply = command(cmd::hincrby, key, field, increment);
return reply::parse<long long>(*reply);
}
double Redis::hincrbyfloat(const StringView &key, const StringView &field, double increment) {
auto reply = command(cmd::hincrbyfloat, key, field, increment);
return reply::parse<double>(*reply);
}
long long Redis::hlen(const StringView &key) {
auto reply = command(cmd::hlen, key);
return reply::parse<long long>(*reply);
}
bool Redis::hset(const StringView &key, const StringView &field, const StringView &val) {
auto reply = command(cmd::hset, key, field, val);
return reply::parse<bool>(*reply);
}
bool Redis::hset(const StringView &key, const std::pair<StringView, StringView> &item) {
return hset(key, item.first, item.second);
}
bool Redis::hsetnx(const StringView &key, const StringView &field, const StringView &val) {
auto reply = command(cmd::hsetnx, key, field, val);
return reply::parse<bool>(*reply);
}
bool Redis::hsetnx(const StringView &key, const std::pair<StringView, StringView> &item) {
return hsetnx(key, item.first, item.second);
}
long long Redis::hstrlen(const StringView &key, const StringView &field) {
auto reply = command(cmd::hstrlen, key, field);
return reply::parse<long long>(*reply);
}
// SET commands.
long long Redis::sadd(const StringView &key, const StringView &member) {
auto reply = command(cmd::sadd, key, member);
return reply::parse<long long>(*reply);
}
long long Redis::scard(const StringView &key) {
auto reply = command(cmd::scard, key);
return reply::parse<long long>(*reply);
}
long long Redis::sdiffstore(const StringView &destination, const StringView &key) {
auto reply = command(cmd::sdiffstore, destination, key);
return reply::parse<long long>(*reply);
}
long long Redis::sinterstore(const StringView &destination, const StringView &key) {
auto reply = command(cmd::sinterstore, destination, key);
return reply::parse<long long>(*reply);
}
bool Redis::sismember(const StringView &key, const StringView &member) {
auto reply = command(cmd::sismember, key, member);
return reply::parse<bool>(*reply);
}
bool Redis::smove(const StringView &source,
const StringView &destination,
const StringView &member) {
auto reply = command(cmd::smove, source, destination, member);
return reply::parse<bool>(*reply);
}
OptionalString Redis::spop(const StringView &key) {
auto reply = command(cmd::spop, key);
return reply::parse<OptionalString>(*reply);
}
OptionalString Redis::srandmember(const StringView &key) {
auto reply = command(cmd::srandmember, key);
return reply::parse<OptionalString>(*reply);
}
long long Redis::srem(const StringView &key, const StringView &member) {
auto reply = command(cmd::srem, key, member);
return reply::parse<long long>(*reply);
}
long long Redis::sunionstore(const StringView &destination, const StringView &key) {
auto reply = command(cmd::sunionstore, destination, key);
return reply::parse<long long>(*reply);
}
// SORTED SET commands.
auto Redis::bzpopmax(const StringView &key, long long timeout)
-> Optional<std::tuple<std::string, std::string, double>> {
auto reply = command(cmd::bzpopmax, key, timeout);
return reply::parse<Optional<std::tuple<std::string, std::string, double>>>(*reply);
}
auto Redis::bzpopmin(const StringView &key, long long timeout)
-> Optional<std::tuple<std::string, std::string, double>> {
auto reply = command(cmd::bzpopmin, key, timeout);
return reply::parse<Optional<std::tuple<std::string, std::string, double>>>(*reply);
}
long long Redis::zadd(const StringView &key,
const StringView &member,
double score,
UpdateType type,
bool changed) {
auto reply = command(cmd::zadd, key, member, score, type, changed);
return reply::parse<long long>(*reply);
}
long long Redis::zcard(const StringView &key) {
auto reply = command(cmd::zcard, key);
return reply::parse<long long>(*reply);
}
double Redis::zincrby(const StringView &key, double increment, const StringView &member) {
auto reply = command(cmd::zincrby, key, increment, member);
return reply::parse<double>(*reply);
}
long long Redis::zinterstore(const StringView &destination, const StringView &key, double weight) {
auto reply = command(cmd::zinterstore, destination, key, weight);
return reply::parse<long long>(*reply);
}
Optional<std::pair<std::string, double>> Redis::zpopmax(const StringView &key) {
auto reply = command(cmd::zpopmax, key, 1);
return reply::parse<Optional<std::pair<std::string, double>>>(*reply);
}
Optional<std::pair<std::string, double>> Redis::zpopmin(const StringView &key) {
auto reply = command(cmd::zpopmin, key, 1);
return reply::parse<Optional<std::pair<std::string, double>>>(*reply);
}
OptionalLongLong Redis::zrank(const StringView &key, const StringView &member) {
auto reply = command(cmd::zrank, key, member);
return reply::parse<OptionalLongLong>(*reply);
}
long long Redis::zrem(const StringView &key, const StringView &member) {
auto reply = command(cmd::zrem, key, member);
return reply::parse<long long>(*reply);
}
long long Redis::zremrangebyrank(const StringView &key, long long start, long long stop) {
auto reply = command(cmd::zremrangebyrank, key, start, stop);
return reply::parse<long long>(*reply);
}
OptionalLongLong Redis::zrevrank(const StringView &key, const StringView &member) {
auto reply = command(cmd::zrevrank, key, member);
return reply::parse<OptionalLongLong>(*reply);
}
OptionalDouble Redis::zscore(const StringView &key, const StringView &member) {
auto reply = command(cmd::zscore, key, member);
return reply::parse<OptionalDouble>(*reply);
}
long long Redis::zunionstore(const StringView &destination, const StringView &key, double weight) {
auto reply = command(cmd::zunionstore, destination, key, weight);
return reply::parse<long long>(*reply);
}
// HYPERLOGLOG commands.
bool Redis::pfadd(const StringView &key, const StringView &element) {
auto reply = command(cmd::pfadd, key, element);
return reply::parse<bool>(*reply);
}
long long Redis::pfcount(const StringView &key) {
auto reply = command(cmd::pfcount, key);
return reply::parse<long long>(*reply);
}
void Redis::pfmerge(const StringView &destination, const StringView &key) {
auto reply = command(cmd::pfmerge, destination, key);
reply::parse<void>(*reply);
}
// GEO commands.
long long Redis::geoadd(const StringView &key,
const std::tuple<StringView, double, double> &member) {
auto reply = command(cmd::geoadd, key, member);
return reply::parse<long long>(*reply);
}
OptionalDouble Redis::geodist(const StringView &key,
const StringView &member1,
const StringView &member2,
GeoUnit unit) {
auto reply = command(cmd::geodist, key, member1, member2, unit);
return reply::parse<OptionalDouble>(*reply);
}
OptionalLongLong Redis::georadius(const StringView &key,
const std::pair<double, double> &loc,
double radius,
GeoUnit unit,
const StringView &destination,
bool store_dist,
long long count) {
auto reply = command(cmd::georadius_store,
key,
loc,
radius,
unit,
destination,
store_dist,
count);
reply::rewrite_georadius_reply(*reply);
return reply::parse<OptionalLongLong>(*reply);
}
OptionalLongLong Redis::georadiusbymember(const StringView &key,
const StringView &member,
double radius,
GeoUnit unit,
const StringView &destination,
bool store_dist,
long long count) {
auto reply = command(cmd::georadiusbymember_store,
key,
member,
radius,
unit,
destination,
store_dist,
count);
reply::rewrite_georadius_reply(*reply);
return reply::parse<OptionalLongLong>(*reply);
}
// SCRIPTING commands.
void Redis::script_flush() {
auto reply = command(cmd::script_flush);
reply::parse<void>(*reply);
}
void Redis::script_kill() {
auto reply = command(cmd::script_kill);
reply::parse<void>(*reply);
}
std::string Redis::script_load(const StringView &script) {
auto reply = command(cmd::script_load, script);
return reply::parse<std::string>(*reply);
}
// PUBSUB commands.
long long Redis::publish(const StringView &channel, const StringView &message) {
auto reply = command(cmd::publish, channel, message);
return reply::parse<long long>(*reply);
}
// Transaction commands.
void Redis::watch(const StringView &key) {
auto reply = command(cmd::watch, key);
reply::parse<void>(*reply);
}
// Stream commands.
long long Redis::xack(const StringView &key, const StringView &group, const StringView &id) {
auto reply = command(cmd::xack, key, group, id);
return reply::parse<long long>(*reply);
}
long long Redis::xdel(const StringView &key, const StringView &id) {
auto reply = command(cmd::xdel, key, id);
return reply::parse<long long>(*reply);
}
void Redis::xgroup_create(const StringView &key,
const StringView &group,
const StringView &id,
bool mkstream) {
auto reply = command(cmd::xgroup_create, key, group, id, mkstream);
reply::parse<void>(*reply);
}
void Redis::xgroup_setid(const StringView &key, const StringView &group, const StringView &id) {
auto reply = command(cmd::xgroup_setid, key, group, id);
reply::parse<void>(*reply);
}
long long Redis::xgroup_destroy(const StringView &key, const StringView &group) {
auto reply = command(cmd::xgroup_destroy, key, group);
return reply::parse<long long>(*reply);
}
long long Redis::xgroup_delconsumer(const StringView &key,
const StringView &group,
const StringView &consumer) {
auto reply = command(cmd::xgroup_delconsumer, key, group, consumer);
return reply::parse<long long>(*reply);
}
long long Redis::xlen(const StringView &key) {
auto reply = command(cmd::xlen, key);
return reply::parse<long long>(*reply);
}
long long Redis::xtrim(const StringView &key, long long count, bool approx) {
auto reply = command(cmd::xtrim, key, count, approx);
return reply::parse<long long>(*reply);
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,769 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "redis_cluster.h"
#include <hiredis/hiredis.h>
#include "command.h"
#include "errors.h"
#include "queued_redis.h"
namespace sw {
namespace redis {
RedisCluster::RedisCluster(const std::string &uri) : RedisCluster(ConnectionOptions(uri)) {}
Redis RedisCluster::redis(const StringView &hash_tag) {
auto opts = _pool.connection_options(hash_tag);
return Redis(std::make_shared<Connection>(opts));
}
Pipeline RedisCluster::pipeline(const StringView &hash_tag) {
auto opts = _pool.connection_options(hash_tag);
return Pipeline(std::make_shared<Connection>(opts));
}
Transaction RedisCluster::transaction(const StringView &hash_tag, bool piped) {
auto opts = _pool.connection_options(hash_tag);
return Transaction(std::make_shared<Connection>(opts), piped);
}
Subscriber RedisCluster::subscriber() {
auto opts = _pool.connection_options();
return Subscriber(Connection(opts));
}
// KEY commands.
long long RedisCluster::del(const StringView &key) {
auto reply = command(cmd::del, key);
return reply::parse<long long>(*reply);
}
OptionalString RedisCluster::dump(const StringView &key) {
auto reply = command(cmd::dump, key);
return reply::parse<OptionalString>(*reply);
}
long long RedisCluster::exists(const StringView &key) {
auto reply = command(cmd::exists, key);
return reply::parse<long long>(*reply);
}
bool RedisCluster::expire(const StringView &key, long long timeout) {
auto reply = command(cmd::expire, key, timeout);
return reply::parse<bool>(*reply);
}
bool RedisCluster::expireat(const StringView &key, long long timestamp) {
auto reply = command(cmd::expireat, key, timestamp);
return reply::parse<bool>(*reply);
}
bool RedisCluster::persist(const StringView &key) {
auto reply = command(cmd::persist, key);
return reply::parse<bool>(*reply);
}
bool RedisCluster::pexpire(const StringView &key, long long timeout) {
auto reply = command(cmd::pexpire, key, timeout);
return reply::parse<bool>(*reply);
}
bool RedisCluster::pexpireat(const StringView &key, long long timestamp) {
auto reply = command(cmd::pexpireat, key, timestamp);
return reply::parse<bool>(*reply);
}
long long RedisCluster::pttl(const StringView &key) {
auto reply = command(cmd::pttl, key);
return reply::parse<long long>(*reply);
}
void RedisCluster::rename(const StringView &key, const StringView &newkey) {
auto reply = command(cmd::rename, key, newkey);
reply::parse<void>(*reply);
}
bool RedisCluster::renamenx(const StringView &key, const StringView &newkey) {
auto reply = command(cmd::renamenx, key, newkey);
return reply::parse<bool>(*reply);
}
void RedisCluster::restore(const StringView &key,
const StringView &val,
long long ttl,
bool replace) {
auto reply = command(cmd::restore, key, val, ttl, replace);
reply::parse<void>(*reply);
}
long long RedisCluster::touch(const StringView &key) {
auto reply = command(cmd::touch, key);
return reply::parse<long long>(*reply);
}
long long RedisCluster::ttl(const StringView &key) {
auto reply = command(cmd::ttl, key);
return reply::parse<long long>(*reply);
}
std::string RedisCluster::type(const StringView &key) {
auto reply = command(cmd::type, key);
return reply::parse<std::string>(*reply);
}
long long RedisCluster::unlink(const StringView &key) {
auto reply = command(cmd::unlink, key);
return reply::parse<long long>(*reply);
}
// STRING commands.
long long RedisCluster::append(const StringView &key, const StringView &val) {
auto reply = command(cmd::append, key, val);
return reply::parse<long long>(*reply);
}
long long RedisCluster::bitcount(const StringView &key, long long start, long long end) {
auto reply = command(cmd::bitcount, key, start, end);
return reply::parse<long long>(*reply);
}
long long RedisCluster::bitop(BitOp op, const StringView &destination, const StringView &key) {
auto reply = _command(cmd::bitop, destination, op, destination, key);
return reply::parse<long long>(*reply);
}
long long RedisCluster::bitpos(const StringView &key,
long long bit,
long long start,
long long end) {
auto reply = command(cmd::bitpos, key, bit, start, end);
return reply::parse<long long>(*reply);
}
long long RedisCluster::decr(const StringView &key) {
auto reply = command(cmd::decr, key);
return reply::parse<long long>(*reply);
}
long long RedisCluster::decrby(const StringView &key, long long decrement) {
auto reply = command(cmd::decrby, key, decrement);
return reply::parse<long long>(*reply);
}
OptionalString RedisCluster::get(const StringView &key) {
auto reply = command(cmd::get, key);
return reply::parse<OptionalString>(*reply);
}
long long RedisCluster::getbit(const StringView &key, long long offset) {
auto reply = command(cmd::getbit, key, offset);
return reply::parse<long long>(*reply);
}
std::string RedisCluster::getrange(const StringView &key, long long start, long long end) {
auto reply = command(cmd::getrange, key, start, end);
return reply::parse<std::string>(*reply);
}
OptionalString RedisCluster::getset(const StringView &key, const StringView &val) {
auto reply = command(cmd::getset, key, val);
return reply::parse<OptionalString>(*reply);
}
long long RedisCluster::incr(const StringView &key) {
auto reply = command(cmd::incr, key);
return reply::parse<long long>(*reply);
}
long long RedisCluster::incrby(const StringView &key, long long increment) {
auto reply = command(cmd::incrby, key, increment);
return reply::parse<long long>(*reply);
}
double RedisCluster::incrbyfloat(const StringView &key, double increment) {
auto reply = command(cmd::incrbyfloat, key, increment);
return reply::parse<double>(*reply);
}
void RedisCluster::psetex(const StringView &key,
long long ttl,
const StringView &val) {
auto reply = command(cmd::psetex, key, ttl, val);
reply::parse<void>(*reply);
}
bool RedisCluster::set(const StringView &key,
const StringView &val,
const std::chrono::milliseconds &ttl,
UpdateType type) {
auto reply = command(cmd::set, key, val, ttl.count(), type);
reply::rewrite_set_reply(*reply);
return reply::parse<bool>(*reply);
}
void RedisCluster::setex(const StringView &key,
long long ttl,
const StringView &val) {
auto reply = command(cmd::setex, key, ttl, val);
reply::parse<void>(*reply);
}
bool RedisCluster::setnx(const StringView &key, const StringView &val) {
auto reply = command(cmd::setnx, key, val);
return reply::parse<bool>(*reply);
}
long long RedisCluster::setrange(const StringView &key, long long offset, const StringView &val) {
auto reply = command(cmd::setrange, key, offset, val);
return reply::parse<long long>(*reply);
}
long long RedisCluster::strlen(const StringView &key) {
auto reply = command(cmd::strlen, key);
return reply::parse<long long>(*reply);
}
// LIST commands.
OptionalStringPair RedisCluster::blpop(const StringView &key, long long timeout) {
auto reply = command(cmd::blpop, key, timeout);
return reply::parse<OptionalStringPair>(*reply);
}
OptionalStringPair RedisCluster::blpop(const StringView &key, const std::chrono::seconds &timeout) {
return blpop(key, timeout.count());
}
OptionalStringPair RedisCluster::brpop(const StringView &key, long long timeout) {
auto reply = command(cmd::brpop, key, timeout);
return reply::parse<OptionalStringPair>(*reply);
}
OptionalStringPair RedisCluster::brpop(const StringView &key, const std::chrono::seconds &timeout) {
return brpop(key, timeout.count());
}
OptionalString RedisCluster::brpoplpush(const StringView &source,
const StringView &destination,
long long timeout) {
auto reply = command(cmd::brpoplpush, source, destination, timeout);
return reply::parse<OptionalString>(*reply);
}
OptionalString RedisCluster::lindex(const StringView &key, long long index) {
auto reply = command(cmd::lindex, key, index);
return reply::parse<OptionalString>(*reply);
}
long long RedisCluster::linsert(const StringView &key,
InsertPosition position,
const StringView &pivot,
const StringView &val) {
auto reply = command(cmd::linsert, key, position, pivot, val);
return reply::parse<long long>(*reply);
}
long long RedisCluster::llen(const StringView &key) {
auto reply = command(cmd::llen, key);
return reply::parse<long long>(*reply);
}
OptionalString RedisCluster::lpop(const StringView &key) {
auto reply = command(cmd::lpop, key);
return reply::parse<OptionalString>(*reply);
}
long long RedisCluster::lpush(const StringView &key, const StringView &val) {
auto reply = command(cmd::lpush, key, val);
return reply::parse<long long>(*reply);
}
long long RedisCluster::lpushx(const StringView &key, const StringView &val) {
auto reply = command(cmd::lpushx, key, val);
return reply::parse<long long>(*reply);
}
long long RedisCluster::lrem(const StringView &key, long long count, const StringView &val) {
auto reply = command(cmd::lrem, key, count, val);
return reply::parse<long long>(*reply);
}
void RedisCluster::lset(const StringView &key, long long index, const StringView &val) {
auto reply = command(cmd::lset, key, index, val);
reply::parse<void>(*reply);
}
void RedisCluster::ltrim(const StringView &key, long long start, long long stop) {
auto reply = command(cmd::ltrim, key, start, stop);
reply::parse<void>(*reply);
}
OptionalString RedisCluster::rpop(const StringView &key) {
auto reply = command(cmd::rpop, key);
return reply::parse<OptionalString>(*reply);
}
OptionalString RedisCluster::rpoplpush(const StringView &source, const StringView &destination) {
auto reply = command(cmd::rpoplpush, source, destination);
return reply::parse<OptionalString>(*reply);
}
long long RedisCluster::rpush(const StringView &key, const StringView &val) {
auto reply = command(cmd::rpush, key, val);
return reply::parse<long long>(*reply);
}
long long RedisCluster::rpushx(const StringView &key, const StringView &val) {
auto reply = command(cmd::rpushx, key, val);
return reply::parse<long long>(*reply);
}
long long RedisCluster::hdel(const StringView &key, const StringView &field) {
auto reply = command(cmd::hdel, key, field);
return reply::parse<long long>(*reply);
}
bool RedisCluster::hexists(const StringView &key, const StringView &field) {
auto reply = command(cmd::hexists, key, field);
return reply::parse<bool>(*reply);
}
OptionalString RedisCluster::hget(const StringView &key, const StringView &field) {
auto reply = command(cmd::hget, key, field);
return reply::parse<OptionalString>(*reply);
}
long long RedisCluster::hincrby(const StringView &key, const StringView &field, long long increment) {
auto reply = command(cmd::hincrby, key, field, increment);
return reply::parse<long long>(*reply);
}
double RedisCluster::hincrbyfloat(const StringView &key, const StringView &field, double increment) {
auto reply = command(cmd::hincrbyfloat, key, field, increment);
return reply::parse<double>(*reply);
}
long long RedisCluster::hlen(const StringView &key) {
auto reply = command(cmd::hlen, key);
return reply::parse<long long>(*reply);
}
bool RedisCluster::hset(const StringView &key, const StringView &field, const StringView &val) {
auto reply = command(cmd::hset, key, field, val);
return reply::parse<bool>(*reply);
}
bool RedisCluster::hset(const StringView &key, const std::pair<StringView, StringView> &item) {
return hset(key, item.first, item.second);
}
bool RedisCluster::hsetnx(const StringView &key, const StringView &field, const StringView &val) {
auto reply = command(cmd::hsetnx, key, field, val);
return reply::parse<bool>(*reply);
}
bool RedisCluster::hsetnx(const StringView &key, const std::pair<StringView, StringView> &item) {
return hsetnx(key, item.first, item.second);
}
long long RedisCluster::hstrlen(const StringView &key, const StringView &field) {
auto reply = command(cmd::hstrlen, key, field);
return reply::parse<long long>(*reply);
}
// SET commands.
long long RedisCluster::sadd(const StringView &key, const StringView &member) {
auto reply = command(cmd::sadd, key, member);
return reply::parse<long long>(*reply);
}
long long RedisCluster::scard(const StringView &key) {
auto reply = command(cmd::scard, key);
return reply::parse<long long>(*reply);
}
long long RedisCluster::sdiffstore(const StringView &destination, const StringView &key) {
auto reply = command(cmd::sdiffstore, destination, key);
return reply::parse<long long>(*reply);
}
long long RedisCluster::sinterstore(const StringView &destination, const StringView &key) {
auto reply = command(cmd::sinterstore, destination, key);
return reply::parse<long long>(*reply);
}
bool RedisCluster::sismember(const StringView &key, const StringView &member) {
auto reply = command(cmd::sismember, key, member);
return reply::parse<bool>(*reply);
}
bool RedisCluster::smove(const StringView &source,
const StringView &destination,
const StringView &member) {
auto reply = command(cmd::smove, source, destination, member);
return reply::parse<bool>(*reply);
}
OptionalString RedisCluster::spop(const StringView &key) {
auto reply = command(cmd::spop, key);
return reply::parse<OptionalString>(*reply);
}
OptionalString RedisCluster::srandmember(const StringView &key) {
auto reply = command(cmd::srandmember, key);
return reply::parse<OptionalString>(*reply);
}
long long RedisCluster::srem(const StringView &key, const StringView &member) {
auto reply = command(cmd::srem, key, member);
return reply::parse<long long>(*reply);
}
long long RedisCluster::sunionstore(const StringView &destination, const StringView &key) {
auto reply = command(cmd::sunionstore, destination, key);
return reply::parse<long long>(*reply);
}
// SORTED SET commands.
auto RedisCluster::bzpopmax(const StringView &key, long long timeout)
-> Optional<std::tuple<std::string, std::string, double>> {
auto reply = command(cmd::bzpopmax, key, timeout);
return reply::parse<Optional<std::tuple<std::string, std::string, double>>>(*reply);
}
auto RedisCluster::bzpopmin(const StringView &key, long long timeout)
-> Optional<std::tuple<std::string, std::string, double>> {
auto reply = command(cmd::bzpopmin, key, timeout);
return reply::parse<Optional<std::tuple<std::string, std::string, double>>>(*reply);
}
long long RedisCluster::zadd(const StringView &key,
const StringView &member,
double score,
UpdateType type,
bool changed) {
auto reply = command(cmd::zadd, key, member, score, type, changed);
return reply::parse<long long>(*reply);
}
long long RedisCluster::zcard(const StringView &key) {
auto reply = command(cmd::zcard, key);
return reply::parse<long long>(*reply);
}
double RedisCluster::zincrby(const StringView &key, double increment, const StringView &member) {
auto reply = command(cmd::zincrby, key, increment, member);
return reply::parse<double>(*reply);
}
long long RedisCluster::zinterstore(const StringView &destination,
const StringView &key,
double weight) {
auto reply = command(cmd::zinterstore, destination, key, weight);
return reply::parse<long long>(*reply);
}
Optional<std::pair<std::string, double>> RedisCluster::zpopmax(const StringView &key) {
auto reply = command(cmd::zpopmax, key, 1);
return reply::parse<Optional<std::pair<std::string, double>>>(*reply);
}
Optional<std::pair<std::string, double>> RedisCluster::zpopmin(const StringView &key) {
auto reply = command(cmd::zpopmin, key, 1);
return reply::parse<Optional<std::pair<std::string, double>>>(*reply);
}
OptionalLongLong RedisCluster::zrank(const StringView &key, const StringView &member) {
auto reply = command(cmd::zrank, key, member);
return reply::parse<OptionalLongLong>(*reply);
}
long long RedisCluster::zrem(const StringView &key, const StringView &member) {
auto reply = command(cmd::zrem, key, member);
return reply::parse<long long>(*reply);
}
long long RedisCluster::zremrangebyrank(const StringView &key, long long start, long long stop) {
auto reply = command(cmd::zremrangebyrank, key, start, stop);
return reply::parse<long long>(*reply);
}
OptionalLongLong RedisCluster::zrevrank(const StringView &key, const StringView &member) {
auto reply = command(cmd::zrevrank, key, member);
return reply::parse<OptionalLongLong>(*reply);
}
OptionalDouble RedisCluster::zscore(const StringView &key, const StringView &member) {
auto reply = command(cmd::zscore, key, member);
return reply::parse<OptionalDouble>(*reply);
}
long long RedisCluster::zunionstore(const StringView &destination,
const StringView &key,
double weight) {
auto reply = command(cmd::zunionstore, destination, key, weight);
return reply::parse<long long>(*reply);
}
// HYPERLOGLOG commands.
bool RedisCluster::pfadd(const StringView &key, const StringView &element) {
auto reply = command(cmd::pfadd, key, element);
return reply::parse<bool>(*reply);
}
long long RedisCluster::pfcount(const StringView &key) {
auto reply = command(cmd::pfcount, key);
return reply::parse<long long>(*reply);
}
void RedisCluster::pfmerge(const StringView &destination, const StringView &key) {
auto reply = command(cmd::pfmerge, destination, key);
reply::parse<void>(*reply);
}
// GEO commands.
long long RedisCluster::geoadd(const StringView &key,
const std::tuple<StringView, double, double> &member) {
auto reply = command(cmd::geoadd, key, member);
return reply::parse<long long>(*reply);
}
OptionalDouble RedisCluster::geodist(const StringView &key,
const StringView &member1,
const StringView &member2,
GeoUnit unit) {
auto reply = command(cmd::geodist, key, member1, member2, unit);
return reply::parse<OptionalDouble>(*reply);
}
OptionalLongLong RedisCluster::georadius(const StringView &key,
const std::pair<double, double> &loc,
double radius,
GeoUnit unit,
const StringView &destination,
bool store_dist,
long long count) {
auto reply = command(cmd::georadius_store,
key,
loc,
radius,
unit,
destination,
store_dist,
count);
reply::rewrite_georadius_reply(*reply);
return reply::parse<OptionalLongLong>(*reply);
}
OptionalLongLong RedisCluster::georadiusbymember(const StringView &key,
const StringView &member,
double radius,
GeoUnit unit,
const StringView &destination,
bool store_dist,
long long count) {
auto reply = command(cmd::georadiusbymember_store,
key,
member,
radius,
unit,
destination,
store_dist,
count);
reply::rewrite_georadius_reply(*reply);
return reply::parse<OptionalLongLong>(*reply);
}
// PUBSUB commands.
long long RedisCluster::publish(const StringView &channel, const StringView &message) {
auto reply = command(cmd::publish, channel, message);
return reply::parse<long long>(*reply);
}
// Stream commands.
long long RedisCluster::xack(const StringView &key, const StringView &group, const StringView &id) {
auto reply = command(cmd::xack, key, group, id);
return reply::parse<long long>(*reply);
}
long long RedisCluster::xdel(const StringView &key, const StringView &id) {
auto reply = command(cmd::xdel, key, id);
return reply::parse<long long>(*reply);
}
void RedisCluster::xgroup_create(const StringView &key,
const StringView &group,
const StringView &id,
bool mkstream) {
auto reply = command(cmd::xgroup_create, key, group, id, mkstream);
reply::parse<void>(*reply);
}
void RedisCluster::xgroup_setid(const StringView &key,
const StringView &group,
const StringView &id) {
auto reply = command(cmd::xgroup_setid, key, group, id);
reply::parse<void>(*reply);
}
long long RedisCluster::xgroup_destroy(const StringView &key, const StringView &group) {
auto reply = command(cmd::xgroup_destroy, key, group);
return reply::parse<long long>(*reply);
}
long long RedisCluster::xgroup_delconsumer(const StringView &key,
const StringView &group,
const StringView &consumer) {
auto reply = command(cmd::xgroup_delconsumer, key, group, consumer);
return reply::parse<long long>(*reply);
}
long long RedisCluster::xlen(const StringView &key) {
auto reply = command(cmd::xlen, key);
return reply::parse<long long>(*reply);
}
long long RedisCluster::xtrim(const StringView &key, long long count, bool approx) {
auto reply = command(cmd::xtrim, key, count, approx);
return reply::parse<long long>(*reply);
}
void RedisCluster::_asking(Connection &connection) {
// Send ASKING command.
connection.send("ASKING");
auto reply = connection.recv();
assert(reply);
reply::parse<void>(*reply);
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,150 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "reply.h"
namespace sw {
namespace redis {
namespace reply {
std::string to_status(redisReply &reply) {
if (!reply::is_status(reply)) {
throw ProtoError("Expect STATUS reply");
}
if (reply.str == nullptr) {
throw ProtoError("A null status reply");
}
// Old version hiredis' *redisReply::len* is of type int.
// So we CANNOT have something like: *return {reply.str, reply.len}*.
return std::string(reply.str, reply.len);
}
std::string parse(ParseTag<std::string>, redisReply &reply) {
if (!reply::is_string(reply) && !reply::is_status(reply)) {
throw ProtoError("Expect STRING reply");
}
if (reply.str == nullptr) {
throw ProtoError("A null string reply");
}
// Old version hiredis' *redisReply::len* is of type int.
// So we CANNOT have something like: *return {reply.str, reply.len}*.
return std::string(reply.str, reply.len);
}
long long parse(ParseTag<long long>, redisReply &reply) {
if (!reply::is_integer(reply)) {
throw ProtoError("Expect INTEGER reply");
}
return reply.integer;
}
double parse(ParseTag<double>, redisReply &reply) {
return std::stod(parse<std::string>(reply));
}
bool parse(ParseTag<bool>, redisReply &reply) {
auto ret = parse<long long>(reply);
if (ret == 1) {
return true;
} else if (ret == 0) {
return false;
} else {
throw ProtoError("Invalid bool reply: " + std::to_string(ret));
}
}
void parse(ParseTag<void>, redisReply &reply) {
if (!reply::is_status(reply)) {
throw ProtoError("Expect STATUS reply");
}
if (reply.str == nullptr) {
throw ProtoError("A null status reply");
}
static const std::string OK = "OK";
// Old version hiredis' *redisReply::len* is of type int.
// So we have to cast it to an unsigned int.
if (static_cast<std::size_t>(reply.len) != OK.size()
|| OK.compare(0, OK.size(), reply.str, reply.len) != 0) {
throw ProtoError("NOT ok status reply: " + reply::to_status(reply));
}
}
void rewrite_set_reply(redisReply &reply) {
if (is_nil(reply)) {
// Failed to set, and make it a FALSE reply.
reply.type = REDIS_REPLY_INTEGER;
reply.integer = 0;
return;
}
// Check if it's a "OK" status reply.
reply::parse<void>(reply);
assert(is_status(reply) && reply.str != nullptr);
free(reply.str);
// Make it a TRUE reply.
reply.type = REDIS_REPLY_INTEGER;
reply.integer = 1;
}
void rewrite_georadius_reply(redisReply &reply) {
if (is_array(reply) && reply.element == nullptr) {
// Make it a nil reply.
reply.type = REDIS_REPLY_NIL;
}
}
namespace detail {
bool is_flat_array(redisReply &reply) {
assert(reply::is_array(reply));
// Empty array reply.
if (reply.element == nullptr || reply.elements == 0) {
return false;
}
auto *sub_reply = reply.element[0];
// Null element.
if (sub_reply == nullptr) {
return false;
}
return !reply::is_array(*sub_reply);
}
}
}
}
}

View file

@ -0,0 +1,363 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_REPLY_H
#define SEWENEW_REDISPLUSPLUS_REPLY_H
#include <cassert>
#include <string>
#include <memory>
#include <functional>
#include <tuple>
#include <hiredis/hiredis.h>
#include "errors.h"
#include "utils.h"
namespace sw {
namespace redis {
struct ReplyDeleter {
void operator()(redisReply *reply) const {
if (reply != nullptr) {
freeReplyObject(reply);
}
}
};
using ReplyUPtr = std::unique_ptr<redisReply, ReplyDeleter>;
namespace reply {
template <typename T>
struct ParseTag {};
template <typename T>
inline T parse(redisReply &reply) {
return parse(ParseTag<T>(), reply);
}
void parse(ParseTag<void>, redisReply &reply);
std::string parse(ParseTag<std::string>, redisReply &reply);
long long parse(ParseTag<long long>, redisReply &reply);
double parse(ParseTag<double>, redisReply &reply);
bool parse(ParseTag<bool>, redisReply &reply);
template <typename T>
Optional<T> parse(ParseTag<Optional<T>>, redisReply &reply);
template <typename T, typename U>
std::pair<T, U> parse(ParseTag<std::pair<T, U>>, redisReply &reply);
template <typename ...Args>
std::tuple<Args...> parse(ParseTag<std::tuple<Args...>>, redisReply &reply);
template <typename T, typename std::enable_if<IsSequenceContainer<T>::value, int>::type = 0>
T parse(ParseTag<T>, redisReply &reply);
template <typename T, typename std::enable_if<IsAssociativeContainer<T>::value, int>::type = 0>
T parse(ParseTag<T>, redisReply &reply);
template <typename Output>
long long parse_scan_reply(redisReply &reply, Output output);
inline bool is_error(redisReply &reply) {
return reply.type == REDIS_REPLY_ERROR;
}
inline bool is_nil(redisReply &reply) {
return reply.type == REDIS_REPLY_NIL;
}
inline bool is_string(redisReply &reply) {
return reply.type == REDIS_REPLY_STRING;
}
inline bool is_status(redisReply &reply) {
return reply.type == REDIS_REPLY_STATUS;
}
inline bool is_integer(redisReply &reply) {
return reply.type == REDIS_REPLY_INTEGER;
}
inline bool is_array(redisReply &reply) {
return reply.type == REDIS_REPLY_ARRAY;
}
std::string to_status(redisReply &reply);
template <typename Output>
void to_array(redisReply &reply, Output output);
// Rewrite set reply to bool type
void rewrite_set_reply(redisReply &reply);
// Rewrite georadius reply to OptionalLongLong type
void rewrite_georadius_reply(redisReply &reply);
template <typename Output>
auto parse_xpending_reply(redisReply &reply, Output output)
-> std::tuple<long long, OptionalString, OptionalString>;
}
// Inline implementations.
namespace reply {
namespace detail {
template <typename Output>
void to_array(redisReply &reply, Output output) {
if (!is_array(reply)) {
throw ProtoError("Expect ARRAY reply");
}
if (reply.element == nullptr) {
// Empty array.
return;
}
for (std::size_t idx = 0; idx != reply.elements; ++idx) {
auto *sub_reply = reply.element[idx];
if (sub_reply == nullptr) {
throw ProtoError("Null array element reply");
}
*output = parse<typename IterType<Output>::type>(*sub_reply);
++output;
}
}
bool is_flat_array(redisReply &reply);
template <typename Output>
void to_flat_array(redisReply &reply, Output output) {
if (reply.element == nullptr) {
// Empty array.
return;
}
if (reply.elements % 2 != 0) {
throw ProtoError("Not string pair array reply");
}
for (std::size_t idx = 0; idx != reply.elements; idx += 2) {
auto *key_reply = reply.element[idx];
auto *val_reply = reply.element[idx + 1];
if (key_reply == nullptr || val_reply == nullptr) {
throw ProtoError("Null string array reply");
}
using Pair = typename IterType<Output>::type;
using FirstType = typename std::decay<typename Pair::first_type>::type;
using SecondType = typename std::decay<typename Pair::second_type>::type;
*output = std::make_pair(parse<FirstType>(*key_reply),
parse<SecondType>(*val_reply));
++output;
}
}
template <typename Output>
void to_array(std::true_type, redisReply &reply, Output output) {
if (is_flat_array(reply)) {
to_flat_array(reply, output);
} else {
to_array(reply, output);
}
}
template <typename Output>
void to_array(std::false_type, redisReply &reply, Output output) {
to_array(reply, output);
}
template <typename T>
std::tuple<T> parse_tuple(redisReply **reply, std::size_t idx) {
assert(reply != nullptr);
auto *sub_reply = reply[idx];
if (sub_reply == nullptr) {
throw ProtoError("Null reply");
}
return std::make_tuple(parse<T>(*sub_reply));
}
template <typename T, typename ...Args>
auto parse_tuple(redisReply **reply, std::size_t idx) ->
typename std::enable_if<sizeof...(Args) != 0, std::tuple<T, Args...>>::type {
assert(reply != nullptr);
return std::tuple_cat(parse_tuple<T>(reply, idx),
parse_tuple<Args...>(reply, idx + 1));
}
}
template <typename T>
Optional<T> parse(ParseTag<Optional<T>>, redisReply &reply) {
if (reply::is_nil(reply)) {
return {};
}
return Optional<T>(parse<T>(reply));
}
template <typename T, typename U>
std::pair<T, U> parse(ParseTag<std::pair<T, U>>, redisReply &reply) {
if (!is_array(reply)) {
throw ProtoError("Expect ARRAY reply");
}
if (reply.elements != 2) {
throw ProtoError("NOT key-value PAIR reply");
}
if (reply.element == nullptr) {
throw ProtoError("Null PAIR reply");
}
auto *first = reply.element[0];
auto *second = reply.element[1];
if (first == nullptr || second == nullptr) {
throw ProtoError("Null pair reply");
}
return std::make_pair(parse<typename std::decay<T>::type>(*first),
parse<typename std::decay<U>::type>(*second));
}
template <typename ...Args>
std::tuple<Args...> parse(ParseTag<std::tuple<Args...>>, redisReply &reply) {
constexpr auto size = sizeof...(Args);
static_assert(size > 0, "DO NOT support parsing tuple with 0 element");
if (!is_array(reply)) {
throw ProtoError("Expect ARRAY reply");
}
if (reply.elements != size) {
throw ProtoError("Expect tuple reply with " + std::to_string(size) + "elements");
}
if (reply.element == nullptr) {
throw ProtoError("Null TUPLE reply");
}
return detail::parse_tuple<Args...>(reply.element, 0);
}
template <typename T, typename std::enable_if<IsSequenceContainer<T>::value, int>::type>
T parse(ParseTag<T>, redisReply &reply) {
if (!is_array(reply)) {
throw ProtoError("Expect ARRAY reply");
}
T container;
to_array(reply, std::back_inserter(container));
return container;
}
template <typename T, typename std::enable_if<IsAssociativeContainer<T>::value, int>::type>
T parse(ParseTag<T>, redisReply &reply) {
if (!is_array(reply)) {
throw ProtoError("Expect ARRAY reply");
}
T container;
to_array(reply, std::inserter(container, container.end()));
return container;
}
template <typename Output>
long long parse_scan_reply(redisReply &reply, Output output) {
if (reply.elements != 2 || reply.element == nullptr) {
throw ProtoError("Invalid scan reply");
}
auto *cursor_reply = reply.element[0];
auto *data_reply = reply.element[1];
if (cursor_reply == nullptr || data_reply == nullptr) {
throw ProtoError("Invalid cursor reply or data reply");
}
auto cursor_str = reply::parse<std::string>(*cursor_reply);
auto new_cursor = 0;
try {
new_cursor = std::stoll(cursor_str);
} catch (const std::exception &e) {
throw ProtoError("Invalid cursor reply: " + cursor_str);
}
reply::to_array(*data_reply, output);
return new_cursor;
}
template <typename Output>
void to_array(redisReply &reply, Output output) {
if (!is_array(reply)) {
throw ProtoError("Expect ARRAY reply");
}
detail::to_array(typename IsKvPairIter<Output>::type(), reply, output);
}
template <typename Output>
auto parse_xpending_reply(redisReply &reply, Output output)
-> std::tuple<long long, OptionalString, OptionalString> {
if (!is_array(reply) || reply.elements != 4) {
throw ProtoError("expect array reply with 4 elements");
}
for (std::size_t idx = 0; idx != reply.elements; ++idx) {
if (reply.element[idx] == nullptr) {
throw ProtoError("null array reply");
}
}
auto num = parse<long long>(*(reply.element[0]));
auto start = parse<OptionalString>(*(reply.element[1]));
auto end = parse<OptionalString>(*(reply.element[2]));
auto &entry_reply = *(reply.element[3]);
if (!is_nil(entry_reply)) {
to_array(entry_reply, output);
}
return std::make_tuple(num, std::move(start), std::move(end));
}
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_REPLY_H

View file

@ -0,0 +1,361 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "sentinel.h"
#include <cassert>
#include <thread>
#include <random>
#include <algorithm>
#include "redis.h"
#include "errors.h"
namespace sw {
namespace redis {
class Sentinel::Iterator {
public:
Iterator(std::list<Connection> &healthy_sentinels,
std::list<ConnectionOptions> &broken_sentinels) :
_healthy_sentinels(healthy_sentinels),
_broken_sentinels(broken_sentinels) {
reset();
}
Connection& next();
void reset();
private:
std::list<Connection> &_healthy_sentinels;
std::size_t _healthy_size = 0;
std::list<ConnectionOptions> &_broken_sentinels;
std::size_t _broken_size = 0;
};
Connection& Sentinel::Iterator::next() {
while (_healthy_size > 0) {
assert(_healthy_sentinels.size() >= _healthy_size);
--_healthy_size;
auto &connection = _healthy_sentinels.front();
if (connection.broken()) {
_broken_sentinels.push_front(connection.options());
++_broken_size;
_healthy_sentinels.pop_front();
} else {
_healthy_sentinels.splice(_healthy_sentinels.end(),
_healthy_sentinels,
_healthy_sentinels.begin());
return _healthy_sentinels.back();
}
}
while (_broken_size > 0) {
assert(_broken_sentinels.size() >= _broken_size);
--_broken_size;
try {
const auto &opt = _broken_sentinels.front();
Connection connection(opt);
_healthy_sentinels.push_back(std::move(connection));
_broken_sentinels.pop_front();
return _healthy_sentinels.back();
} catch (const Error &e) {
// Failed to connect to sentinel.
_broken_sentinels.splice(_broken_sentinels.end(),
_broken_sentinels,
_broken_sentinels.begin());
}
}
throw StopIterError();
}
void Sentinel::Iterator::reset() {
_healthy_size = _healthy_sentinels.size();
_broken_size = _broken_sentinels.size();
}
Sentinel::Sentinel(const SentinelOptions &sentinel_opts) :
_broken_sentinels(_parse_options(sentinel_opts)),
_sentinel_opts(sentinel_opts) {
if (_sentinel_opts.connect_timeout == std::chrono::milliseconds(0)
|| _sentinel_opts.socket_timeout == std::chrono::milliseconds(0)) {
throw Error("With sentinel, connection timeout and socket timeout cannot be 0");
}
}
Connection Sentinel::master(const std::string &master_name, const ConnectionOptions &opts) {
std::lock_guard<std::mutex> lock(_mutex);
Iterator iter(_healthy_sentinels, _broken_sentinels);
std::size_t retries = 0;
while (true) {
try {
auto &sentinel = iter.next();
auto master = _get_master_addr_by_name(sentinel, master_name);
if (!master) {
// Try the next sentinel.
continue;
}
auto connection = _connect_redis(*master, opts);
if (_get_role(connection) != Role::MASTER) {
// Retry the whole process at most SentinelOptions::max_retry times.
++retries;
if (retries > _sentinel_opts.max_retry) {
throw Error("Failed to get master from sentinel");
}
std::this_thread::sleep_for(_sentinel_opts.retry_interval);
// Restart the iteration.
iter.reset();
continue;
}
return connection;
} catch (const StopIterError &e) {
throw;
} catch (const Error &e) {
continue;
}
}
}
Connection Sentinel::slave(const std::string &master_name, const ConnectionOptions &opts) {
std::lock_guard<std::mutex> lock(_mutex);
Iterator iter(_healthy_sentinels, _broken_sentinels);
std::size_t retries = 0;
while (true) {
try {
auto &sentinel = iter.next();
auto slaves = _get_slave_addr_by_name(sentinel, master_name);
if (slaves.empty()) {
// Try the next sentinel.
continue;
}
// Normally slaves list is NOT very large, so there won't be a performance problem.
auto slave_iter = std::find(slaves.begin(),
slaves.end(),
Node{opts.host, opts.port});
if (slave_iter != slaves.end() && slave_iter != slaves.begin()) {
// The given node is still a valid slave. Try it first.
std::swap(*(slaves.begin()), *slave_iter);
}
for (const auto &slave : slaves) {
try {
auto connection = _connect_redis(slave, opts);
if (_get_role(connection) != Role::SLAVE) {
// Retry the whole process at most SentinelOptions::max_retry times.
++retries;
if (retries > _sentinel_opts.max_retry) {
throw Error("Failed to get slave from sentinel");
}
std::this_thread::sleep_for(_sentinel_opts.retry_interval);
// Restart the iteration.
iter.reset();
break;
}
return connection;
} catch (const Error &e) {
// Try the next slave.
continue;
}
}
} catch (const StopIterError &e) {
throw;
} catch (const Error &e) {
continue;
}
}
}
Optional<Node> Sentinel::_get_master_addr_by_name(Connection &connection, const StringView &name) {
connection.send("SENTINEL GET-MASTER-ADDR-BY-NAME %b", name.data(), name.size());
auto reply = connection.recv();
assert(reply);
auto master = reply::parse<Optional<std::pair<std::string, std::string>>>(*reply);
if (!master) {
return {};
}
int port = 0;
try {
port = std::stoi(master->second);
} catch (const std::exception &) {
throw ProtoError("Master port is invalid: " + master->second);
}
return Optional<Node>{Node{master->first, port}};
}
std::vector<Node> Sentinel::_get_slave_addr_by_name(Connection &connection,
const StringView &name) {
try {
connection.send("SENTINEL SLAVES %b", name.data(), name.size());
auto reply = connection.recv();
assert(reply);
auto slaves = _parse_slave_info(*reply);
// Make slave list random.
std::mt19937 gen(std::random_device{}());
std::shuffle(slaves.begin(), slaves.end(), gen);
return slaves;
} catch (const ReplyError &e) {
// Unknown master name.
return {};
}
}
std::vector<Node> Sentinel::_parse_slave_info(redisReply &reply) const {
using SlaveInfo = std::unordered_map<std::string, std::string>;
auto slaves = reply::parse<std::vector<SlaveInfo>>(reply);
std::vector<Node> nodes;
for (const auto &slave : slaves) {
auto flags_iter = slave.find("flags");
auto ip_iter = slave.find("ip");
auto port_iter = slave.find("port");
if (flags_iter == slave.end() || ip_iter == slave.end() || port_iter == slave.end()) {
throw ProtoError("Invalid slave info");
}
// This slave is down, e.g. 's_down,slave,disconnected'
if (flags_iter->second != "slave") {
continue;
}
int port = 0;
try {
port = std::stoi(port_iter->second);
} catch (const std::exception &) {
throw ProtoError("Slave port is invalid: " + port_iter->second);
}
nodes.push_back(Node{ip_iter->second, port});
}
return nodes;
}
Connection Sentinel::_connect_redis(const Node &node, ConnectionOptions opts) {
opts.host = node.host;
opts.port = node.port;
return Connection(opts);
}
Role Sentinel::_get_role(Connection &connection) {
connection.send("INFO REPLICATION");
auto reply = connection.recv();
assert(reply);
auto info = reply::parse<std::string>(*reply);
auto start = info.find("role:");
if (start == std::string::npos) {
throw ProtoError("Invalid INFO REPLICATION reply");
}
start += 5;
auto stop = info.find("\r\n", start);
if (stop == std::string::npos) {
throw ProtoError("Invalid INFO REPLICATION reply");
}
auto role = info.substr(start, stop - start);
if (role == "master") {
return Role::MASTER;
} else if (role == "slave") {
return Role::SLAVE;
} else {
throw Error("Invalid role: " + role);
}
}
std::list<ConnectionOptions> Sentinel::_parse_options(const SentinelOptions &opts) const {
std::list<ConnectionOptions> options;
for (const auto &node : opts.nodes) {
ConnectionOptions opt;
opt.host = node.first;
opt.port = node.second;
opt.password = opts.password;
opt.keep_alive = opts.keep_alive;
opt.connect_timeout = opts.connect_timeout;
opt.socket_timeout = opts.socket_timeout;
options.push_back(opt);
}
return options;
}
SimpleSentinel::SimpleSentinel(const std::shared_ptr<Sentinel> &sentinel,
const std::string &master_name,
Role role) :
_sentinel(sentinel),
_master_name(master_name),
_role(role) {
if (!_sentinel) {
throw Error("Sentinel cannot be null");
}
if (_role != Role::MASTER && _role != Role::SLAVE) {
throw Error("Role must be Role::MASTER or Role::SLAVE");
}
}
Connection SimpleSentinel::create(const ConnectionOptions &opts) {
assert(_sentinel);
if (_role == Role::MASTER) {
return _sentinel->master(_master_name, opts);
}
assert(_role == Role::SLAVE);
return _sentinel->slave(_master_name, opts);
}
}
}

View file

@ -0,0 +1,138 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_SENTINEL_H
#define SEWENEW_REDISPLUSPLUS_SENTINEL_H
#include <string>
#include <list>
#include <vector>
#include <memory>
#include <mutex>
#include "connection.h"
#include "shards.h"
#include "reply.h"
namespace sw {
namespace redis {
struct SentinelOptions {
std::vector<std::pair<std::string, int>> nodes;
std::string password;
bool keep_alive = true;
std::chrono::milliseconds connect_timeout{100};
std::chrono::milliseconds socket_timeout{100};
std::chrono::milliseconds retry_interval{100};
std::size_t max_retry = 2;
};
class Sentinel {
public:
explicit Sentinel(const SentinelOptions &sentinel_opts);
Sentinel(const Sentinel &) = delete;
Sentinel& operator=(const Sentinel &) = delete;
Sentinel(Sentinel &&) = delete;
Sentinel& operator=(Sentinel &&) = delete;
~Sentinel() = default;
private:
Connection master(const std::string &master_name, const ConnectionOptions &opts);
Connection slave(const std::string &master_name, const ConnectionOptions &opts);
class Iterator;
friend class SimpleSentinel;
std::list<ConnectionOptions> _parse_options(const SentinelOptions &opts) const;
Optional<Node> _get_master_addr_by_name(Connection &connection, const StringView &name);
std::vector<Node> _get_slave_addr_by_name(Connection &connection, const StringView &name);
Connection _connect_redis(const Node &node, ConnectionOptions opts);
Role _get_role(Connection &connection);
std::vector<Node> _parse_slave_info(redisReply &reply) const;
std::list<Connection> _healthy_sentinels;
std::list<ConnectionOptions> _broken_sentinels;
SentinelOptions _sentinel_opts;
std::mutex _mutex;
};
class SimpleSentinel {
public:
SimpleSentinel(const std::shared_ptr<Sentinel> &sentinel,
const std::string &master_name,
Role role);
SimpleSentinel() = default;
SimpleSentinel(const SimpleSentinel &) = default;
SimpleSentinel& operator=(const SimpleSentinel &) = default;
SimpleSentinel(SimpleSentinel &&) = default;
SimpleSentinel& operator=(SimpleSentinel &&) = default;
~SimpleSentinel() = default;
explicit operator bool() const {
return bool(_sentinel);
}
Connection create(const ConnectionOptions &opts);
private:
std::shared_ptr<Sentinel> _sentinel;
std::string _master_name;
Role _role = Role::MASTER;
};
class StopIterError : public Error {
public:
StopIterError() : Error("StopIterError") {}
StopIterError(const StopIterError &) = default;
StopIterError& operator=(const StopIterError &) = default;
StopIterError(StopIterError &&) = default;
StopIterError& operator=(StopIterError &&) = default;
virtual ~StopIterError() = default;
};
}
}
#endif // end SEWENEW_REDISPLUSPLUS_SENTINEL_H

View file

@ -0,0 +1,50 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "shards.h"
namespace sw {
namespace redis {
RedirectionError::RedirectionError(const std::string &msg): ReplyError(msg) {
std::tie(_slot, _node) = _parse_error(msg);
}
std::pair<Slot, Node> RedirectionError::_parse_error(const std::string &msg) const {
// "slot ip:port"
auto space_pos = msg.find(" ");
auto colon_pos = msg.find(":");
if (space_pos == std::string::npos
|| colon_pos == std::string::npos
|| colon_pos < space_pos) {
throw ProtoError("Invalid ASK error message: " + msg);
}
try {
auto slot = std::stoull(msg.substr(0, space_pos));
auto host = msg.substr(space_pos + 1, colon_pos - space_pos - 1);
auto port = std::stoi(msg.substr(colon_pos + 1));
return {slot, {host, port}};
} catch (const std::exception &e) {
throw ProtoError("Invalid ASK error message: " + msg);
}
}
}
}

View file

@ -0,0 +1,115 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_H
#define SEWENEW_REDISPLUSPLUS_SHARDS_H
#include <string>
#include <map>
#include "errors.h"
namespace sw {
namespace redis {
using Slot = std::size_t;
struct SlotRange {
Slot min;
Slot max;
};
inline bool operator<(const SlotRange &lhs, const SlotRange &rhs) {
return lhs.max < rhs.max;
}
struct Node {
std::string host;
int port;
};
inline bool operator==(const Node &lhs, const Node &rhs) {
return lhs.host == rhs.host && lhs.port == rhs.port;
}
struct NodeHash {
std::size_t operator()(const Node &node) const noexcept {
auto host_hash = std::hash<std::string>{}(node.host);
auto port_hash = std::hash<int>{}(node.port);
return host_hash ^ (port_hash << 1);
}
};
using Shards = std::map<SlotRange, Node>;
class RedirectionError : public ReplyError {
public:
RedirectionError(const std::string &msg);
RedirectionError(const RedirectionError &) = default;
RedirectionError& operator=(const RedirectionError &) = default;
RedirectionError(RedirectionError &&) = default;
RedirectionError& operator=(RedirectionError &&) = default;
virtual ~RedirectionError() = default;
Slot slot() const {
return _slot;
}
const Node& node() const {
return _node;
}
private:
std::pair<Slot, Node> _parse_error(const std::string &msg) const;
Slot _slot = 0;
Node _node;
};
class MovedError : public RedirectionError {
public:
explicit MovedError(const std::string &msg) : RedirectionError(msg) {}
MovedError(const MovedError &) = default;
MovedError& operator=(const MovedError &) = default;
MovedError(MovedError &&) = default;
MovedError& operator=(MovedError &&) = default;
virtual ~MovedError() = default;
};
class AskError : public RedirectionError {
public:
explicit AskError(const std::string &msg) : RedirectionError(msg) {}
AskError(const AskError &) = default;
AskError& operator=(const AskError &) = default;
AskError(AskError &&) = default;
AskError& operator=(AskError &&) = default;
virtual ~AskError() = default;
};
}
}
#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_H

View file

@ -0,0 +1,319 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "shards_pool.h"
#include <unordered_set>
#include "errors.h"
namespace sw {
namespace redis {
const std::size_t ShardsPool::SHARDS;
ShardsPool::ShardsPool(const ConnectionPoolOptions &pool_opts,
const ConnectionOptions &connection_opts) :
_pool_opts(pool_opts),
_connection_opts(connection_opts) {
if (_connection_opts.type != ConnectionType::TCP) {
throw Error("Only support TCP connection for Redis Cluster");
}
Connection connection(_connection_opts);
_shards = _cluster_slots(connection);
_init_pool(_shards);
}
ShardsPool::ShardsPool(ShardsPool &&that) {
std::lock_guard<std::mutex> lock(that._mutex);
_move(std::move(that));
}
ShardsPool& ShardsPool::operator=(ShardsPool &&that) {
if (this != &that) {
std::lock(_mutex, that._mutex);
std::lock_guard<std::mutex> lock_this(_mutex, std::adopt_lock);
std::lock_guard<std::mutex> lock_that(that._mutex, std::adopt_lock);
_move(std::move(that));
}
return *this;
}
GuardedConnection ShardsPool::fetch(const StringView &key) {
auto slot = _slot(key);
return _fetch(slot);
}
GuardedConnection ShardsPool::fetch() {
auto slot = _slot();
return _fetch(slot);
}
GuardedConnection ShardsPool::fetch(const Node &node) {
std::lock_guard<std::mutex> lock(_mutex);
auto iter = _pools.find(node);
if (iter == _pools.end()) {
// Node doesn't exist, and it should be a newly created node.
// So add a new connection pool.
iter = _add_node(node);
}
assert(iter != _pools.end());
return GuardedConnection(iter->second);
}
void ShardsPool::update() {
// My might send command to a removed node.
// Try at most 3 times.
for (auto idx = 0; idx < 3; ++idx) {
try {
// Randomly pick a connection.
auto guarded_connection = fetch();
auto shards = _cluster_slots(guarded_connection.connection());
std::unordered_set<Node, NodeHash> nodes;
for (const auto &shard : shards) {
nodes.insert(shard.second);
}
std::lock_guard<std::mutex> lock(_mutex);
// TODO: If shards is unchanged, no need to update, and return immediately.
_shards = std::move(shards);
// Remove non-existent nodes.
for (auto iter = _pools.begin(); iter != _pools.end(); ) {
if (nodes.find(iter->first) == nodes.end()) {
// Node has been removed.
_pools.erase(iter++);
} else {
++iter;
}
}
// Add connection pool for new nodes.
// In fact, connections will be created lazily.
for (const auto &node : nodes) {
if (_pools.find(node) == _pools.end()) {
_add_node(node);
}
}
// Update successfully.
return;
} catch (const Error &) {
// continue;
}
}
throw Error("Failed to update shards info");
}
ConnectionOptions ShardsPool::connection_options(const StringView &key) {
auto slot = _slot(key);
return _connection_options(slot);
}
ConnectionOptions ShardsPool::connection_options() {
auto slot = _slot();
return _connection_options(slot);
}
void ShardsPool::_move(ShardsPool &&that) {
_pool_opts = that._pool_opts;
_connection_opts = that._connection_opts;
_shards = std::move(that._shards);
_pools = std::move(that._pools);
}
void ShardsPool::_init_pool(const Shards &shards) {
for (const auto &shard : shards) {
_add_node(shard.second);
}
}
Shards ShardsPool::_cluster_slots(Connection &connection) const {
auto reply = _cluster_slots_command(connection);
assert(reply);
return _parse_reply(*reply);
}
ReplyUPtr ShardsPool::_cluster_slots_command(Connection &connection) const {
connection.send("CLUSTER SLOTS");
return connection.recv();
}
Shards ShardsPool::_parse_reply(redisReply &reply) const {
if (!reply::is_array(reply)) {
throw ProtoError("Expect ARRAY reply");
}
if (reply.element == nullptr || reply.elements == 0) {
throw Error("Empty slots");
}
Shards shards;
for (std::size_t idx = 0; idx != reply.elements; ++idx) {
auto *sub_reply = reply.element[idx];
if (sub_reply == nullptr) {
throw ProtoError("Null slot info");
}
shards.emplace(_parse_slot_info(*sub_reply));
}
return shards;
}
std::pair<SlotRange, Node> ShardsPool::_parse_slot_info(redisReply &reply) const {
if (reply.elements < 3 || reply.element == nullptr) {
throw ProtoError("Invalid slot info");
}
// Min slot id
auto *min_slot_reply = reply.element[0];
if (min_slot_reply == nullptr) {
throw ProtoError("Invalid min slot");
}
std::size_t min_slot = reply::parse<long long>(*min_slot_reply);
// Max slot id
auto *max_slot_reply = reply.element[1];
if (max_slot_reply == nullptr) {
throw ProtoError("Invalid max slot");
}
std::size_t max_slot = reply::parse<long long>(*max_slot_reply);
if (min_slot > max_slot) {
throw ProtoError("Invalid slot range");
}
// Master node info
auto *node_reply = reply.element[2];
if (node_reply == nullptr
|| !reply::is_array(*node_reply)
|| node_reply->element == nullptr
|| node_reply->elements < 2) {
throw ProtoError("Invalid node info");
}
auto master_host = reply::parse<std::string>(*(node_reply->element[0]));
int master_port = reply::parse<long long>(*(node_reply->element[1]));
// By now, we ignore node id and other replicas' info.
return {SlotRange{min_slot, max_slot}, Node{master_host, master_port}};
}
Slot ShardsPool::_slot(const StringView &key) const {
// The following code is copied from: https://redis.io/topics/cluster-spec
// And I did some minor changes.
const auto *k = key.data();
auto keylen = key.size();
// start-end indexes of { and }.
std::size_t s = 0;
std::size_t e = 0;
// Search the first occurrence of '{'.
for (s = 0; s < keylen; s++)
if (k[s] == '{') break;
// No '{' ? Hash the whole key. This is the base case.
if (s == keylen) return crc16(k, keylen) & SHARDS;
// '{' found? Check if we have the corresponding '}'.
for (e = s + 1; e < keylen; e++)
if (k[e] == '}') break;
// No '}' or nothing between {} ? Hash the whole key.
if (e == keylen || e == s + 1) return crc16(k, keylen) & SHARDS;
// If we are here there is both a { and a } on its right. Hash
// what is in the middle between { and }.
return crc16(k + s + 1, e - s - 1) & SHARDS;
}
Slot ShardsPool::_slot() const {
static thread_local std::default_random_engine engine;
std::uniform_int_distribution<std::size_t> uniform_dist(0, SHARDS);
return uniform_dist(engine);
}
ConnectionPoolSPtr& ShardsPool::_get_pool(Slot slot) {
auto shards_iter = _shards.lower_bound(SlotRange{slot, slot});
if (shards_iter == _shards.end() || slot < shards_iter->first.min) {
throw Error("Slot is out of range: " + std::to_string(slot));
}
const auto &node = shards_iter->second;
auto node_iter = _pools.find(node);
if (node_iter == _pools.end()) {
throw Error("Slot is NOT covered: " + std::to_string(slot));
}
return node_iter->second;
}
GuardedConnection ShardsPool::_fetch(Slot slot) {
std::lock_guard<std::mutex> lock(_mutex);
auto &pool = _get_pool(slot);
assert(pool);
return GuardedConnection(pool);
}
ConnectionOptions ShardsPool::_connection_options(Slot slot) {
std::lock_guard<std::mutex> lock(_mutex);
auto &pool = _get_pool(slot);
assert(pool);
return pool->connection_options();
}
auto ShardsPool::_add_node(const Node &node) -> NodeMap::iterator {
auto opts = _connection_opts;
opts.host = node.host;
opts.port = node.port;
return _pools.emplace(node, std::make_shared<ConnectionPool>(_pool_opts, opts)).first;
}
}
}

View file

@ -0,0 +1,137 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H
#define SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H
#include <cassert>
#include <unordered_map>
#include <string>
#include <random>
#include <memory>
#include "reply.h"
#include "connection_pool.h"
#include "shards.h"
namespace sw {
namespace redis {
using ConnectionPoolSPtr = std::shared_ptr<ConnectionPool>;
class GuardedConnection {
public:
GuardedConnection(const ConnectionPoolSPtr &pool) : _pool(pool),
_connection(_pool->fetch()) {
assert(!_connection.broken());
}
GuardedConnection(const GuardedConnection &) = delete;
GuardedConnection& operator=(const GuardedConnection &) = delete;
GuardedConnection(GuardedConnection &&) = default;
GuardedConnection& operator=(GuardedConnection &&) = default;
~GuardedConnection() {
_pool->release(std::move(_connection));
}
Connection& connection() {
return _connection;
}
private:
ConnectionPoolSPtr _pool;
Connection _connection;
};
class ShardsPool {
public:
ShardsPool() = default;
ShardsPool(const ShardsPool &that) = delete;
ShardsPool& operator=(const ShardsPool &that) = delete;
ShardsPool(ShardsPool &&that);
ShardsPool& operator=(ShardsPool &&that);
~ShardsPool() = default;
ShardsPool(const ConnectionPoolOptions &pool_opts,
const ConnectionOptions &connection_opts);
// Fetch a connection by key.
GuardedConnection fetch(const StringView &key);
// Randomly pick a connection.
GuardedConnection fetch();
// Fetch a connection by node.
GuardedConnection fetch(const Node &node);
void update();
ConnectionOptions connection_options(const StringView &key);
ConnectionOptions connection_options();
private:
void _move(ShardsPool &&that);
void _init_pool(const Shards &shards);
Shards _cluster_slots(Connection &connection) const;
ReplyUPtr _cluster_slots_command(Connection &connection) const;
Shards _parse_reply(redisReply &reply) const;
std::pair<SlotRange, Node> _parse_slot_info(redisReply &reply) const;
// Get slot by key.
std::size_t _slot(const StringView &key) const;
// Randomly pick a slot.
std::size_t _slot() const;
ConnectionPoolSPtr& _get_pool(Slot slot);
GuardedConnection _fetch(Slot slot);
ConnectionOptions _connection_options(Slot slot);
using NodeMap = std::unordered_map<Node, ConnectionPoolSPtr, NodeHash>;
NodeMap::iterator _add_node(const Node &node);
ConnectionPoolOptions _pool_opts;
ConnectionOptions _connection_opts;
Shards _shards;
NodeMap _pools;
std::mutex _mutex;
static const std::size_t SHARDS = 16383;
};
}
}
#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H

View file

@ -0,0 +1,222 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "subscriber.h"
#include <cassert>
namespace sw {
namespace redis {
const Subscriber::TypeIndex Subscriber::_msg_type_index = {
{"message", MsgType::MESSAGE},
{"pmessage", MsgType::PMESSAGE},
{"subscribe", MsgType::SUBSCRIBE},
{"unsubscribe", MsgType::UNSUBSCRIBE},
{"psubscribe", MsgType::PSUBSCRIBE},
{"punsubscribe", MsgType::PUNSUBSCRIBE}
};
Subscriber::Subscriber(Connection connection) : _connection(std::move(connection)) {}
void Subscriber::subscribe(const StringView &channel) {
_check_connection();
// TODO: cmd::subscribe DOES NOT send the subscribe message to Redis.
// In fact, it puts the command to network buffer.
// So we need a queue to record these sub or unsub commands, and
// ensure that before stopping the subscriber, all these commands
// have really been sent to Redis.
cmd::subscribe(_connection, channel);
}
void Subscriber::unsubscribe() {
_check_connection();
cmd::unsubscribe(_connection);
}
void Subscriber::unsubscribe(const StringView &channel) {
_check_connection();
cmd::unsubscribe(_connection, channel);
}
void Subscriber::psubscribe(const StringView &pattern) {
_check_connection();
cmd::psubscribe(_connection, pattern);
}
void Subscriber::punsubscribe() {
_check_connection();
cmd::punsubscribe(_connection);
}
void Subscriber::punsubscribe(const StringView &pattern) {
_check_connection();
cmd::punsubscribe(_connection, pattern);
}
void Subscriber::consume() {
_check_connection();
ReplyUPtr reply;
try {
reply = _connection.recv();
} catch (const TimeoutError &) {
_connection.reset();
throw;
}
assert(reply);
if (!reply::is_array(*reply) || reply->elements < 1 || reply->element == nullptr) {
throw ProtoError("Invalid subscribe message");
}
auto type = _msg_type(reply->element[0]);
switch (type) {
case MsgType::MESSAGE:
_handle_message(*reply);
break;
case MsgType::PMESSAGE:
_handle_pmessage(*reply);
break;
case MsgType::SUBSCRIBE:
case MsgType::UNSUBSCRIBE:
case MsgType::PSUBSCRIBE:
case MsgType::PUNSUBSCRIBE:
_handle_meta(type, *reply);
break;
default:
assert(false);
}
}
Subscriber::MsgType Subscriber::_msg_type(redisReply *reply) const {
if (reply == nullptr) {
throw ProtoError("Null type reply.");
}
auto type = reply::parse<std::string>(*reply);
auto iter = _msg_type_index.find(type);
if (iter == _msg_type_index.end()) {
throw ProtoError("Invalid message type.");
}
return iter->second;
}
void Subscriber::_check_connection() {
if (_connection.broken()) {
throw Error("Connection is broken");
}
}
void Subscriber::_handle_message(redisReply &reply) {
if (_msg_callback == nullptr) {
return;
}
if (reply.elements != 3) {
throw ProtoError("Expect 3 sub replies");
}
assert(reply.element != nullptr);
auto *channel_reply = reply.element[1];
if (channel_reply == nullptr) {
throw ProtoError("Null channel reply");
}
auto channel = reply::parse<std::string>(*channel_reply);
auto *msg_reply = reply.element[2];
if (msg_reply == nullptr) {
throw ProtoError("Null message reply");
}
auto msg = reply::parse<std::string>(*msg_reply);
_msg_callback(std::move(channel), std::move(msg));
}
void Subscriber::_handle_pmessage(redisReply &reply) {
if (_pmsg_callback == nullptr) {
return;
}
if (reply.elements != 4) {
throw ProtoError("Expect 4 sub replies");
}
assert(reply.element != nullptr);
auto *pattern_reply = reply.element[1];
if (pattern_reply == nullptr) {
throw ProtoError("Null pattern reply");
}
auto pattern = reply::parse<std::string>(*pattern_reply);
auto *channel_reply = reply.element[2];
if (channel_reply == nullptr) {
throw ProtoError("Null channel reply");
}
auto channel = reply::parse<std::string>(*channel_reply);
auto *msg_reply = reply.element[3];
if (msg_reply == nullptr) {
throw ProtoError("Null message reply");
}
auto msg = reply::parse<std::string>(*msg_reply);
_pmsg_callback(std::move(pattern), std::move(channel), std::move(msg));
}
void Subscriber::_handle_meta(MsgType type, redisReply &reply) {
if (_meta_callback == nullptr) {
return;
}
if (reply.elements != 3) {
throw ProtoError("Expect 3 sub replies");
}
assert(reply.element != nullptr);
auto *channel_reply = reply.element[1];
if (channel_reply == nullptr) {
throw ProtoError("Null channel reply");
}
auto channel = reply::parse<OptionalString>(*channel_reply);
auto *num_reply = reply.element[2];
if (num_reply == nullptr) {
throw ProtoError("Null num reply");
}
auto num = reply::parse<long long>(*num_reply);
_meta_callback(type, std::move(channel), num);
}
}
}

View file

@ -0,0 +1,231 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H
#define SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H
#include <unordered_map>
#include <string>
#include <functional>
#include "connection.h"
#include "reply.h"
#include "command.h"
#include "utils.h"
namespace sw {
namespace redis {
// @NOTE: Subscriber is NOT thread-safe.
// Subscriber uses callbacks to handle messages. There are 6 kinds of messages:
// 1) MESSAGE: message sent to a channel.
// 2) PMESSAGE: message sent to channels of a given pattern.
// 3) SUBSCRIBE: meta message sent when we successfully subscribe to a channel.
// 4) UNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel.
// 5) PSUBSCRIBE: meta message sent when we successfully subscribe to a channel pattern.
// 6) PUNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel pattern.
//
// Use Subscriber::on_message(MsgCallback) to set the callback function for message of
// *MESSAGE* type, and the callback interface is:
// void (std::string channel, std::string msg)
//
// Use Subscriber::on_pmessage(PatternMsgCallback) to set the callback function for message of
// *PMESSAGE* type, and the callback interface is:
// void (std::string pattern, std::string channel, std::string msg)
//
// Messages of other types are called *META MESSAGE*, they have the same callback interface.
// Use Subscriber::on_meta(MetaCallback) to set the callback function:
// void (Subscriber::MsgType type, OptionalString channel, long long num)
//
// NOTE: If we haven't subscribe/psubscribe to any channel/pattern, and try to
// unsubscribe/punsubscribe without any parameter, i.e. unsubscribe/punsubscribe all
// channels/patterns, *channel* will be null. So the second parameter of meta callback
// is of type *OptionalString*.
//
// All these callback interfaces pass std::string by value, and you can take their ownership
// (i.e. std::move) safely.
//
// If you don't set callback for a specific kind of message, Subscriber::consume() will
// receive the message, and ignore it, i.e. no callback will be called.
class Subscriber {
public:
Subscriber(const Subscriber &) = delete;
Subscriber& operator=(const Subscriber &) = delete;
Subscriber(Subscriber &&) = default;
Subscriber& operator=(Subscriber &&) = default;
~Subscriber() = default;
enum class MsgType {
SUBSCRIBE,
UNSUBSCRIBE,
PSUBSCRIBE,
PUNSUBSCRIBE,
MESSAGE,
PMESSAGE
};
template <typename MsgCb>
void on_message(MsgCb msg_callback);
template <typename PMsgCb>
void on_pmessage(PMsgCb pmsg_callback);
template <typename MetaCb>
void on_meta(MetaCb meta_callback);
void subscribe(const StringView &channel);
template <typename Input>
void subscribe(Input first, Input last);
template <typename T>
void subscribe(std::initializer_list<T> channels) {
subscribe(channels.begin(), channels.end());
}
void unsubscribe();
void unsubscribe(const StringView &channel);
template <typename Input>
void unsubscribe(Input first, Input last);
template <typename T>
void unsubscribe(std::initializer_list<T> channels) {
unsubscribe(channels.begin(), channels.end());
}
void psubscribe(const StringView &pattern);
template <typename Input>
void psubscribe(Input first, Input last);
template <typename T>
void psubscribe(std::initializer_list<T> channels) {
psubscribe(channels.begin(), channels.end());
}
void punsubscribe();
void punsubscribe(const StringView &channel);
template <typename Input>
void punsubscribe(Input first, Input last);
template <typename T>
void punsubscribe(std::initializer_list<T> channels) {
punsubscribe(channels.begin(), channels.end());
}
void consume();
private:
friend class Redis;
friend class RedisCluster;
explicit Subscriber(Connection connection);
MsgType _msg_type(redisReply *reply) const;
void _check_connection();
void _handle_message(redisReply &reply);
void _handle_pmessage(redisReply &reply);
void _handle_meta(MsgType type, redisReply &reply);
using MsgCallback = std::function<void (std::string channel, std::string msg)>;
using PatternMsgCallback = std::function<void (std::string pattern,
std::string channel,
std::string msg)>;
using MetaCallback = std::function<void (MsgType type,
OptionalString channel,
long long num)>;
using TypeIndex = std::unordered_map<std::string, MsgType>;
static const TypeIndex _msg_type_index;
Connection _connection;
MsgCallback _msg_callback = nullptr;
PatternMsgCallback _pmsg_callback = nullptr;
MetaCallback _meta_callback = nullptr;
};
template <typename MsgCb>
void Subscriber::on_message(MsgCb msg_callback) {
_msg_callback = msg_callback;
}
template <typename PMsgCb>
void Subscriber::on_pmessage(PMsgCb pmsg_callback) {
_pmsg_callback = pmsg_callback;
}
template <typename MetaCb>
void Subscriber::on_meta(MetaCb meta_callback) {
_meta_callback = meta_callback;
}
template <typename Input>
void Subscriber::subscribe(Input first, Input last) {
if (first == last) {
return;
}
_check_connection();
cmd::subscribe_range(_connection, first, last);
}
template <typename Input>
void Subscriber::unsubscribe(Input first, Input last) {
_check_connection();
cmd::unsubscribe_range(_connection, first, last);
}
template <typename Input>
void Subscriber::psubscribe(Input first, Input last) {
if (first == last) {
return;
}
_check_connection();
cmd::psubscribe_range(_connection, first, last);
}
template <typename Input>
void Subscriber::punsubscribe(Input first, Input last) {
_check_connection();
cmd::punsubscribe_range(_connection, first, last);
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H

View file

@ -0,0 +1,123 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "transaction.h"
#include "command.h"
namespace sw {
namespace redis {
std::vector<ReplyUPtr> TransactionImpl::exec(Connection &connection, std::size_t cmd_num) {
_close_transaction();
_get_queued_replies(connection, cmd_num);
return _exec(connection);
}
void TransactionImpl::discard(Connection &connection, std::size_t cmd_num) {
_close_transaction();
_get_queued_replies(connection, cmd_num);
_discard(connection);
}
void TransactionImpl::_open_transaction(Connection &connection) {
assert(!_in_transaction);
cmd::multi(connection);
auto reply = connection.recv();
auto status = reply::to_status(*reply);
if (status != "OK") {
throw Error("Failed to open transaction: " + status);
}
_in_transaction = true;
}
void TransactionImpl::_close_transaction() {
if (!_in_transaction) {
throw Error("No command in transaction");
}
_in_transaction = false;
}
void TransactionImpl::_get_queued_reply(Connection &connection) {
auto reply = connection.recv();
auto status = reply::to_status(*reply);
if (status != "QUEUED") {
throw Error("Invalid QUEUED reply: " + status);
}
}
void TransactionImpl::_get_queued_replies(Connection &connection, std::size_t cmd_num) {
if (_piped) {
// Get all QUEUED reply
while (cmd_num > 0) {
_get_queued_reply(connection);
--cmd_num;
}
}
}
std::vector<ReplyUPtr> TransactionImpl::_exec(Connection &connection) {
cmd::exec(connection);
auto reply = connection.recv();
if (reply::is_nil(*reply)) {
// Execution has been aborted, i.e. watched key has been modified.
throw WatchError();
}
if (!reply::is_array(*reply)) {
throw ProtoError("Expect ARRAY reply");
}
if (reply->element == nullptr || reply->elements == 0) {
// Since we don't allow EXEC without any command, this ARRAY reply
// should NOT be null or empty.
throw ProtoError("Null ARRAY reply");
}
std::vector<ReplyUPtr> replies;
for (std::size_t idx = 0; idx != reply->elements; ++idx) {
auto *sub_reply = reply->element[idx];
if (sub_reply == nullptr) {
throw ProtoError("Null sub reply");
}
auto r = ReplyUPtr(sub_reply);
reply->element[idx] = nullptr;
replies.push_back(std::move(r));
}
return replies;
}
void TransactionImpl::_discard(Connection &connection) {
cmd::discard(connection);
auto reply = connection.recv();
reply::parse<void>(*reply);
}
}
}

View file

@ -0,0 +1,77 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TRANSACTION_H
#define SEWENEW_REDISPLUSPLUS_TRANSACTION_H
#include <cassert>
#include <vector>
#include "connection.h"
#include "errors.h"
namespace sw {
namespace redis {
class TransactionImpl {
public:
explicit TransactionImpl(bool piped) : _piped(piped) {}
template <typename Cmd, typename ...Args>
void command(Connection &connection, Cmd cmd, Args &&...args);
std::vector<ReplyUPtr> exec(Connection &connection, std::size_t cmd_num);
void discard(Connection &connection, std::size_t cmd_num);
private:
void _open_transaction(Connection &connection);
void _close_transaction();
void _get_queued_reply(Connection &connection);
void _get_queued_replies(Connection &connection, std::size_t cmd_num);
std::vector<ReplyUPtr> _exec(Connection &connection);
void _discard(Connection &connection);
bool _in_transaction = false;
bool _piped;
};
template <typename Cmd, typename ...Args>
void TransactionImpl::command(Connection &connection, Cmd cmd, Args &&...args) {
assert(!connection.broken());
if (!_in_transaction) {
_open_transaction(connection);
}
cmd(connection, std::forward<Args>(args)...);
if (!_piped) {
_get_queued_reply(connection);
}
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_TRANSACTION_H

View file

@ -0,0 +1,269 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_UTILS_H
#define SEWENEW_REDISPLUSPLUS_UTILS_H
#include <cstring>
#include <string>
#include <type_traits>
namespace sw {
namespace redis {
// By now, not all compilers support std::string_view,
// so we make our own implementation.
class StringView {
public:
constexpr StringView() noexcept = default;
constexpr StringView(const char *data, std::size_t size) : _data(data), _size(size) {}
StringView(const char *data) : _data(data), _size(std::strlen(data)) {}
StringView(const std::string &str) : _data(str.data()), _size(str.size()) {}
constexpr StringView(const StringView &) noexcept = default;
StringView& operator=(const StringView &) noexcept = default;
constexpr const char* data() const noexcept {
return _data;
}
constexpr std::size_t size() const noexcept {
return _size;
}
private:
const char *_data = nullptr;
std::size_t _size = 0;
};
template <typename T>
class Optional {
public:
Optional() = default;
Optional(const Optional &) = default;
Optional& operator=(const Optional &) = default;
Optional(Optional &&) = default;
Optional& operator=(Optional &&) = default;
~Optional() = default;
template <typename ...Args>
explicit Optional(Args &&...args) : _value(true, T(std::forward<Args>(args)...)) {}
explicit operator bool() const {
return _value.first;
}
T& value() {
return _value.second;
}
const T& value() const {
return _value.second;
}
T* operator->() {
return &(_value.second);
}
const T* operator->() const {
return &(_value.second);
}
T& operator*() {
return _value.second;
}
const T& operator*() const {
return _value.second;
}
private:
std::pair<bool, T> _value;
};
using OptionalString = Optional<std::string>;
using OptionalLongLong = Optional<long long>;
using OptionalDouble = Optional<double>;
using OptionalStringPair = Optional<std::pair<std::string, std::string>>;
template <typename ...>
struct IsKvPair : std::false_type {};
template <typename T, typename U>
struct IsKvPair<std::pair<T, U>> : std::true_type {};
template <typename ...>
using Void = void;
template <typename T, typename U = Void<>>
struct IsInserter : std::false_type {};
template <typename T>
//struct IsInserter<T, Void<typename T::container_type>> : std::true_type {};
struct IsInserter<T,
typename std::enable_if<!std::is_void<typename T::container_type>::value>::type>
: std::true_type {};
template <typename Iter, typename T = Void<>>
struct IterType {
using type = typename std::iterator_traits<Iter>::value_type;
};
template <typename Iter>
//struct IterType<Iter, Void<typename Iter::container_type>> {
struct IterType<Iter,
//typename std::enable_if<std::is_void<typename Iter::value_type>::value>::type> {
typename std::enable_if<IsInserter<Iter>::value>::type> {
using type = typename std::decay<typename Iter::container_type::value_type>::type;
};
template <typename Iter, typename T = Void<>>
struct IsIter : std::false_type {};
template <typename Iter>
struct IsIter<Iter, typename std::enable_if<IsInserter<Iter>::value>::type> : std::true_type {};
template <typename Iter>
//struct IsIter<Iter, Void<typename std::iterator_traits<Iter>::iterator_category>>
struct IsIter<Iter,
typename std::enable_if<!std::is_void<
typename std::iterator_traits<Iter>::value_type>::value>::type>
: std::integral_constant<bool, !std::is_convertible<Iter, StringView>::value> {};
template <typename T>
struct IsKvPairIter : IsKvPair<typename IterType<T>::type> {};
template <typename T, typename Tuple>
struct TupleWithType : std::false_type {};
template <typename T>
struct TupleWithType<T, std::tuple<>> : std::false_type {};
template <typename T, typename U, typename ...Args>
struct TupleWithType<T, std::tuple<U, Args...>> : TupleWithType<T, std::tuple<Args...>> {};
template <typename T, typename ...Args>
struct TupleWithType<T, std::tuple<T, Args...>> : std::true_type {};
template <std::size_t ...Is>
struct IndexSequence {};
template <std::size_t I, std::size_t ...Is>
struct MakeIndexSequence : MakeIndexSequence<I - 1, I - 1, Is...> {};
template <std::size_t ...Is>
struct MakeIndexSequence<0, Is...> : IndexSequence<Is...> {};
// NthType and NthValue are taken from
// https://stackoverflow.com/questions/14261183
template <std::size_t I, typename ...Args>
struct NthType {};
template <typename Arg, typename ...Args>
struct NthType<0, Arg, Args...> {
using type = Arg;
};
template <std::size_t I, typename Arg, typename ...Args>
struct NthType<I, Arg, Args...> {
using type = typename NthType<I - 1, Args...>::type;
};
template <typename ...Args>
struct LastType {
using type = typename NthType<sizeof...(Args) - 1, Args...>::type;
};
struct InvalidLastType {};
template <>
struct LastType<> {
using type = InvalidLastType;
};
template <std::size_t I, typename Arg, typename ...Args>
auto NthValue(Arg &&arg, Args &&...)
-> typename std::enable_if<(I == 0), decltype(std::forward<Arg>(arg))>::type {
return std::forward<Arg>(arg);
}
template <std::size_t I, typename Arg, typename ...Args>
auto NthValue(Arg &&, Args &&...args)
-> typename std::enable_if<(I > 0),
decltype(std::forward<typename NthType<I, Arg, Args...>::type>(
std::declval<typename NthType<I, Arg, Args...>::type>()))>::type {
return std::forward<typename NthType<I, Arg, Args...>::type>(
NthValue<I - 1>(std::forward<Args>(args)...));
}
template <typename ...Args>
auto LastValue(Args &&...args)
-> decltype(std::forward<typename LastType<Args...>::type>(
std::declval<typename LastType<Args...>::type>())) {
return std::forward<typename LastType<Args...>::type>(
NthValue<sizeof...(Args) - 1>(std::forward<Args>(args)...));
}
template <typename T, typename = Void<>>
struct HasPushBack : std::false_type {};
template <typename T>
struct HasPushBack<T,
typename std::enable_if<
std::is_void<decltype(
std::declval<T>().push_back(std::declval<typename T::value_type>())
)>::value>::type> : std::true_type {};
template <typename T, typename = Void<>>
struct HasInsert : std::false_type {};
template <typename T>
struct HasInsert<T,
typename std::enable_if<
std::is_same<
decltype(std::declval<T>().insert(std::declval<typename T::const_iterator>(),
std::declval<typename T::value_type>())),
typename T::iterator>::value>::type> : std::true_type {};
template <typename T>
struct IsSequenceContainer
: std::integral_constant<bool,
HasPushBack<T>::value
&& !std::is_same<typename std::decay<T>::type, std::string>::value> {};
template <typename T>
struct IsAssociativeContainer
: std::integral_constant<bool,
HasInsert<T>::value && !HasPushBack<T>::value> {};
uint16_t crc16(const char *buf, int len);
}
}
#endif // end SEWENEW_REDISPLUSPLUS_UTILS_H