Skip to content

Commit d27ff4b

Browse files
committed
Allow returning strings as raw
1 parent ea2afe1 commit d27ff4b

File tree

8 files changed

+75
-20
lines changed

8 files changed

+75
-20
lines changed

R/redis.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ redis_connect_unix <- function(path, timeout = NULL) {
2525
.Call(Credux_redis_connect_unix, path, as.integer(timeout))
2626
}
2727

28-
redis_command <- function(ptr, command) {
29-
.Call(Credux_redis_command, ptr, command)
28+
redis_command <- function(ptr, command, as = NULL) {
29+
.Call(Credux_redis_command, ptr, command, as)
3030
}
3131

3232
redis_pipeline <- function(ptr, list) {

src/connection.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,18 @@ SEXP redux_redis_connect_unix(SEXP r_path, SEXP r_timeout) {
5454
return extPtr;
5555
}
5656

57-
SEXP redux_redis_command(SEXP extPtr, SEXP cmd) {
57+
SEXP redux_redis_command(SEXP extPtr, SEXP cmd, SEXP r_as) {
5858
redisContext *context = redis_get_context(extPtr, true);
5959

60+
reply_string_as as = r_reply_string_as(r_as);
61+
6062
cmd = PROTECT(redis_check_command(cmd));
6163
const char **argv = NULL;
6264
size_t *argvlen = NULL;
6365
const size_t argc = sexp_to_redis(cmd, &argv, &argvlen);
6466

6567
redisReply *reply = redisCommandArgv(context, argc, argv, argvlen);
66-
SEXP ret = PROTECT(redis_reply_to_sexp(reply, true));
68+
SEXP ret = PROTECT(redis_reply_to_sexp(reply, true, as));
6769
freeReplyObject(reply);
6870
UNPROTECT(2);
6971
return ret;
@@ -94,7 +96,7 @@ SEXP redux_redis_pipeline(SEXP extPtr, SEXP list) {
9496
SEXP ret = PROTECT(allocVector(VECSXP, nc));
9597
for (size_t i = 0; i < nc; ++i) {
9698
redisGetReply(context, (void*)&reply);
97-
SET_VECTOR_ELT(ret, i, redis_reply_to_sexp(reply, false));
99+
SET_VECTOR_ELT(ret, i, redis_reply_to_sexp(reply, false, AS_AUTO));
98100
freeReplyObject(reply);
99101
}
100102
UNPROTECT(2);

src/connection.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
SEXP redux_redis_connect(SEXP host, SEXP port, SEXP timeout);
77
SEXP redux_redis_connect_unix(SEXP path, SEXP timeout);
88

9-
SEXP redux_redis_command(SEXP extPtr, SEXP cmd);
9+
SEXP redux_redis_command(SEXP extPtr, SEXP cmd, SEXP r_as);
1010

1111
SEXP redux_redis_pipeline(SEXP extPtr, SEXP list);
1212

src/conversions.c

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,40 @@
11
#include "conversions.h"
22

3-
SEXP redis_reply_to_sexp(redisReply* reply, bool error_throw) {
3+
reply_string_as r_reply_string_as(SEXP r_as) {
4+
if (r_as == R_NilValue) {
5+
return AS_AUTO;
6+
}
7+
8+
if (TYPEOF(r_as) == STRSXP && length(r_as) == 1) {
9+
if (strcmp(CHAR(STRING_ELT(r_as, 0)), "raw") == 0) {
10+
return AS_RAW;
11+
} else if (strcmp(CHAR(STRING_ELT(r_as, 0)), "auto") == 0) {
12+
return AS_AUTO;
13+
}
14+
}
15+
16+
Rf_error("Invalid option for 'as'");
17+
return AS_AUTO; // #nocov
18+
}
19+
20+
SEXP redis_reply_to_sexp(redisReply* reply, bool error_throw,
21+
reply_string_as as) {
422
if (reply == NULL) {
523
error("Failure communicating with the Redis server");
624
}
725
switch(reply->type) {
826
case REDIS_REPLY_STATUS:
927
return status_to_sexp(reply->str);
1028
case REDIS_REPLY_STRING:
11-
return raw_string_to_sexp(reply->str, reply->len);
29+
return raw_string_to_sexp(reply->str, reply->len, as);
1230
case REDIS_REPLY_INTEGER:
1331
return (reply->integer < INT_MAX ?
1432
ScalarInteger(reply->integer) :
1533
ScalarReal((double)reply->integer));
1634
case REDIS_REPLY_NIL:
1735
return R_NilValue;
1836
case REDIS_REPLY_ARRAY:
19-
return array_to_sexp(reply, error_throw);
37+
return array_to_sexp(reply, error_throw, as);
2038
case REDIS_REPLY_ERROR:
2139
return reply_error(reply, error_throw);
2240
default:
@@ -225,7 +243,7 @@ bool is_raw_string(const char* str, size_t len) {
225243
return false;
226244
}
227245

228-
SEXP raw_string_to_sexp(const char* str, size_t len) {
246+
SEXP raw_string_to_sexp(const char* str, size_t len, reply_string_as as) {
229247
// There are different approaches here to detecting a raw string; we
230248
// can test for presence of a nul byte, but that involves a
231249
// traversal of _every_ string. It really should be corect though
@@ -234,7 +252,7 @@ SEXP raw_string_to_sexp(const char* str, size_t len) {
234252
// The strategy here is to check for a serialised object, then
235253
// assume a string, but fall back on re-encoding as RAW (with an
236254
// extra copy) if a nul byte is found
237-
bool is_raw = is_raw_string(str, len);
255+
bool is_raw = as == AS_RAW || is_raw_string(str, len);
238256
SEXP ret;
239257
if (is_raw) {
240258
ret = PROTECT(allocVector(RAWSXP, len));
@@ -261,12 +279,12 @@ SEXP status_to_sexp(const char* str) {
261279
return ret;
262280
}
263281

264-
SEXP array_to_sexp(redisReply* reply, bool error_throw) {
282+
SEXP array_to_sexp(redisReply* reply, bool error_throw, reply_string_as as) {
265283
SEXP ret = PROTECT(allocVector(VECSXP, reply->elements));
266284
size_t i;
267285
for (i = 0; i < reply->elements; ++i) {
268286
SET_VECTOR_ELT(ret, i,
269-
redis_reply_to_sexp(reply->element[i], error_throw));
287+
redis_reply_to_sexp(reply->element[i], error_throw, as));
270288
}
271289
UNPROTECT(1);
272290
return ret;

src/conversions.h

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@
33
#include <hiredis.h>
44
#include <stdbool.h>
55

6+
typedef enum {
7+
AS_AUTO,
8+
AS_RAW
9+
} reply_string_as;
10+
11+
reply_string_as r_reply_string_as(SEXP as);
12+
613
/* whole reply */
7-
SEXP redis_reply_to_sexp(redisReply* reply, bool error_throw);
14+
SEXP redis_reply_to_sexp(redisReply* reply, bool error_throw,
15+
reply_string_as as);
816

917
/* possible bits of a reply */
10-
SEXP raw_string_to_sexp(const char* s, size_t len);
18+
SEXP raw_string_to_sexp(const char* s, size_t len, reply_string_as as);
1119
SEXP status_to_sexp(const char* s);
12-
SEXP array_to_sexp(redisReply* reply, bool error_throw);
20+
SEXP array_to_sexp(redisReply* reply, bool error_throw, reply_string_as as);
1321
SEXP reply_error(redisReply* reply, bool error_throw);
1422

1523
/* detection */

src/registration.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ static const R_CallMethodDef callMethods[] = {
1111
{"Credux_redis_connect", (DL_FUNC) &redux_redis_connect, 3},
1212
{"Credux_redis_connect_unix", (DL_FUNC) &redux_redis_connect_unix, 2},
1313

14-
{"Credux_redis_command", (DL_FUNC) &redux_redis_command, 2},
14+
{"Credux_redis_command", (DL_FUNC) &redux_redis_command, 3},
1515

1616
{"Credux_redis_pipeline", (DL_FUNC) &redux_redis_pipeline, 2},
1717

src/subscribe.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ SEXP redux_redis_subscribe(SEXP extPtr, SEXP channel, SEXP pattern,
88
SET_VECTOR_ELT(cmd, 0, mkString(p ? "PSUBSCRIBE" : "SUBSCRIBE"));
99
SET_VECTOR_ELT(cmd, 1, channel);
1010
cmd = PROTECT(redis_check_command(cmd));
11-
SEXP ret = PROTECT(redux_redis_command(extPtr, cmd));
11+
SEXP ret = PROTECT(redux_redis_command(extPtr, cmd, R_NilValue));
1212

1313
redux_redis_subscribe_loop(redis_get_context(extPtr, true),
1414
p, callback, envir);
@@ -39,7 +39,7 @@ void redux_redis_subscribe_loop(redisContext* context, int pattern,
3939
while (keep_going) {
4040
R_CheckUserInterrupt();
4141
redisGetReply(context, (void*)&reply);
42-
SEXP x = PROTECT(redis_reply_to_sexp(reply, false));
42+
SEXP x = PROTECT(redis_reply_to_sexp(reply, false, AS_AUTO));
4343
setAttrib(x, R_NamesSymbol, nms);
4444
SETCADR(call, x);
4545
freeReplyObject(reply);
@@ -90,7 +90,7 @@ SEXP redux_redis_unsubscribe(SEXP extPtr, SEXP channel, SEXP pattern) {
9090
n_discarded++;
9191
redisGetReply(context, (void*)&reply);
9292
}
93-
SEXP ret = PROTECT(redis_reply_to_sexp(reply, true));
93+
SEXP ret = PROTECT(redis_reply_to_sexp(reply, true, AS_AUTO));
9494
freeReplyObject(reply);
9595
if (n_discarded > 0) {
9696
SEXP key = PROTECT(mkString("n_discarded"));

tests/testthat/test-redis.R

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,30 @@ test_that("pointer commands are safe", {
176176
expect_error(redis_command(ptr_null, "PING"),
177177
"Context is not connected")
178178
})
179+
180+
181+
test_that("can get raw strings back if asked", {
182+
skip_if_no_redis()
183+
ptr <- redis_connect_tcp(REDIS_HOST, REDIS_PORT)
184+
key <- rand_str()
185+
value <- rand_str()
186+
187+
expect_equal(redis_command(ptr, list("SET", key, value)),
188+
redis_status("OK"))
189+
190+
expect_equal(redis_command(ptr, list("GET", key), "auto"),
191+
value)
192+
expect_equal(redis_command(ptr, list("GET", key), "raw"),
193+
charToRaw(value))
194+
195+
expect_error(redis_command(ptr, list("GET", key), "other"),
196+
"Invalid option for 'as'")
197+
expect_error(redis_command(ptr, list("GET", key), TRUE),
198+
"Invalid option for 'as'")
199+
expect_error(redis_command(ptr, list("GET", key), letters),
200+
"Invalid option for 'as'")
201+
expect_error(redis_command(ptr, list("GET", key), character()),
202+
"Invalid option for 'as'")
203+
204+
expect_equal(redis_command(ptr, c("DEL", key)), 1L)
205+
})

0 commit comments

Comments
 (0)