Skip to content

Commit fa77935

Browse files
Merge pull request #68 from nihohit/resp3-origin
Enable RESP3 types and connections.
2 parents eb8d19d + 78858c5 commit fa77935

35 files changed

+1466
-466
lines changed

Makefile

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,15 @@ test:
99
@REDISRS_SERVER_TYPE=tcp RUST_BACKTRACE=1 cargo test -p redis --no-default-features -- --nocapture --test-threads=1
1010

1111
@echo "===================================================================="
12-
@echo "Testing Connection Type TCP with all features"
12+
@echo "Testing Connection Type TCP with all features and RESP2"
1313
@echo "===================================================================="
1414
@REDISRS_SERVER_TYPE=tcp RUST_BACKTRACE=1 cargo test -p redis --all-features -- --nocapture --test-threads=1 --skip test_module
1515

16+
@echo "===================================================================="
17+
@echo "Testing Connection Type TCP with all features and RESP3"
18+
@echo "===================================================================="
19+
@REDISRS_SERVER_TYPE=tcp RESP3=true cargo test -p redis --all-features -- --nocapture --test-threads=1 --skip test_module
20+
1621
@echo "===================================================================="
1722
@echo "Testing Connection Type TCP with all features and Rustls support"
1823
@echo "===================================================================="
@@ -36,12 +41,12 @@ test:
3641
@echo "===================================================================="
3742
@echo "Testing async-std with Rustls"
3843
@echo "===================================================================="
39-
@REDISRS_SERVER_TYPE=tcp RUST_BACKTRACE=1 cargo test -p redis --features=async-std-rustls-comp,cluster-async -- --nocapture --test-threads=1
44+
@REDISRS_SERVER_TYPE=tcp RUST_BACKTRACE=1 cargo test -p redis --features=async-std-rustls-comp,cluster-async -- --nocapture --test-threads=1 --skip test_module
4045

4146
@echo "===================================================================="
4247
@echo "Testing async-std with native-TLS"
4348
@echo "===================================================================="
44-
@REDISRS_SERVER_TYPE=tcp RUST_BACKTRACE=1 cargo test -p redis --features=async-std-native-tls-comp,cluster-async -- --nocapture --test-threads=1
49+
@REDISRS_SERVER_TYPE=tcp RUST_BACKTRACE=1 cargo test -p redis --features=async-std-native-tls-comp,cluster-async -- --nocapture --test-threads=1 --skip test_module
4550

4651
@echo "===================================================================="
4752
@echo "Testing redis-test"

redis-test/src/lib.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use std::collections::VecDeque;
2727
use std::iter::FromIterator;
2828
use std::sync::{Arc, Mutex};
2929

30-
use redis::{Cmd, ConnectionLike, ErrorKind, Pipeline, RedisError, RedisResult, Value};
30+
use redis::{Cmd, ConnectionLike, ErrorKind, Pipeline, PushKind, RedisError, RedisResult, Value};
3131

3232
#[cfg(feature = "aio")]
3333
use futures::{future, FutureExt};
@@ -45,26 +45,26 @@ pub trait IntoRedisValue {
4545

4646
impl IntoRedisValue for String {
4747
fn into_redis_value(self) -> Value {
48-
Value::Data(self.as_bytes().to_vec())
48+
Value::BulkString(self.as_bytes().to_vec())
4949
}
5050
}
5151

5252
impl IntoRedisValue for &str {
5353
fn into_redis_value(self) -> Value {
54-
Value::Data(self.as_bytes().to_vec())
54+
Value::BulkString(self.as_bytes().to_vec())
5555
}
5656
}
5757

5858
#[cfg(feature = "bytes")]
5959
impl IntoRedisValue for bytes::Bytes {
6060
fn into_redis_value(self) -> Value {
61-
Value::Data(self.to_vec())
61+
Value::BulkString(self.to_vec())
6262
}
6363
}
6464

6565
impl IntoRedisValue for Vec<u8> {
6666
fn into_redis_value(self) -> Value {
67-
Value::Data(self)
67+
Value::BulkString(self)
6868
}
6969
}
7070

@@ -257,6 +257,10 @@ impl ConnectionLike for MockRedisConnection {
257257
fn is_open(&self) -> bool {
258258
true
259259
}
260+
261+
fn execute_push_message(&mut self, _kind: PushKind, _data: Vec<Value>) {
262+
// TODO - implement handling RESP3 push messages
263+
}
260264
}
261265

262266
#[cfg(feature = "aio")]
@@ -311,7 +315,7 @@ mod tests {
311315
cmd("SET").arg("bar").arg("foo").execute(&mut conn);
312316
assert_eq!(
313317
cmd("GET").arg("bar").query(&mut conn),
314-
Ok(Value::Data(b"foo".as_ref().into()))
318+
Ok(Value::BulkString(b"foo".as_ref().into()))
315319
);
316320
}
317321

@@ -402,10 +406,10 @@ mod tests {
402406
fn pipeline_atomic_test() {
403407
let mut conn = MockRedisConnection::new(vec![MockCmd::with_values(
404408
pipe().atomic().cmd("GET").arg("foo").cmd("GET").arg("bar"),
405-
Ok(vec![Value::Bulk(
409+
Ok(vec![Value::Array(
406410
vec!["hello", "world"]
407411
.into_iter()
408-
.map(|x| Value::Data(x.as_bytes().into()))
412+
.map(|x| Value::BulkString(x.as_bytes().into()))
409413
.collect(),
410414
)]),
411415
)]);

redis/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,12 @@ serde_json = { version = "1.0.82", optional = true }
8888

8989
# Optional aHash support
9090
ahash = { version = "0.8.6", optional = true }
91+
num-bigint = "0.4.3"
9192

9293
tracing = "0.1"
9394
futures-time = { version = "3.0.0", optional = true }
9495
arcstr = "1.1.5"
96+
ordered-float = "4.1.1"
9597

9698
[features]
9799
default = ["acl", "streams", "geospatial", "script", "keep-alive"]

redis/benches/bench_basic.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,12 @@ fn bench_decode_simple(b: &mut Bencher, input: &[u8]) {
254254
b.iter(|| redis::parse_redis_value(input).unwrap());
255255
}
256256
fn bench_decode(c: &mut Criterion) {
257-
let value = Value::Bulk(vec![
257+
let value = Value::Array(vec![
258258
Value::Okay,
259-
Value::Status("testing".to_string()),
260-
Value::Bulk(vec![]),
259+
Value::SimpleString("testing".to_string()),
260+
Value::Array(vec![]),
261261
Value::Nil,
262-
Value::Data(vec![b'a'; 10]),
262+
Value::BulkString(vec![b'a'; 10]),
263263
Value::Int(7512182390),
264264
]);
265265

redis/examples/streams.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ fn read_records(client: &redis::Client) -> RedisResult<()> {
220220
for StreamId { id, map } in ids {
221221
println!("\tID {id}");
222222
for (n, s) in map {
223-
if let Value::Data(bytes) = s {
223+
if let Value::BulkString(bytes) = s {
224224
println!("\t\t{}: {}", n, String::from_utf8(bytes).expect("utf8"))
225225
} else {
226226
panic!("Weird data")

redis/src/acl.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,11 @@ impl FromRedisValue for AclInfo {
159159
let flags = flags
160160
.as_sequence()
161161
.ok_or_else(|| {
162-
not_convertible_error!(flags, "Expect a bulk response of ACL flags")
162+
not_convertible_error!(flags, "Expect an array response of ACL flags")
163163
})?
164164
.iter()
165165
.map(|flag| match flag {
166-
Value::Data(flag) => match flag.as_slice() {
166+
Value::BulkString(flag) => match flag.as_slice() {
167167
b"on" => Ok(Rule::On),
168168
b"off" => Ok(Rule::Off),
169169
b"allkeys" => Ok(Rule::AllKeys),
@@ -181,14 +181,14 @@ impl FromRedisValue for AclInfo {
181181
let passwords = passwords
182182
.as_sequence()
183183
.ok_or_else(|| {
184-
not_convertible_error!(flags, "Expect a bulk response of ACL flags")
184+
not_convertible_error!(flags, "Expect an array response of ACL flags")
185185
})?
186186
.iter()
187187
.map(|pass| Ok(Rule::AddHashedPass(String::from_redis_value(pass)?)))
188188
.collect::<RedisResult<_>>()?;
189189

190190
let commands = match commands {
191-
Value::Data(cmd) => std::str::from_utf8(cmd)?,
191+
Value::BulkString(cmd) => std::str::from_utf8(cmd)?,
192192
_ => {
193193
return Err(not_convertible_error!(
194194
commands,
@@ -281,18 +281,18 @@ mod tests {
281281

282282
#[test]
283283
fn test_from_redis_value() {
284-
let redis_value = Value::Bulk(vec![
285-
Value::Data("flags".into()),
286-
Value::Bulk(vec![
287-
Value::Data("on".into()),
288-
Value::Data("allchannels".into()),
284+
let redis_value = Value::Array(vec![
285+
Value::BulkString("flags".into()),
286+
Value::Array(vec![
287+
Value::BulkString("on".into()),
288+
Value::BulkString("allchannels".into()),
289289
]),
290-
Value::Data("passwords".into()),
291-
Value::Bulk(vec![]),
292-
Value::Data("commands".into()),
293-
Value::Data("-@all +get".into()),
294-
Value::Data("keys".into()),
295-
Value::Bulk(vec![Value::Data("pat:*".into())]),
290+
Value::BulkString("passwords".into()),
291+
Value::Array(vec![]),
292+
Value::BulkString("commands".into()),
293+
Value::BulkString("-@all +get".into()),
294+
Value::BulkString("keys".into()),
295+
Value::Array(vec![Value::BulkString("pat:*".into())]),
296296
]);
297297
let acl_info = AclInfo::from_redis_value(&redis_value).expect("Parse successfully");
298298

redis/src/aio/connection.rs

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ use super::async_std;
33
use super::ConnectionLike;
44
use super::{setup_connection, AsyncStream, RedisRuntime};
55
use crate::cmd::{cmd, Cmd};
6-
use crate::connection::{ConnectionAddr, ConnectionInfo, Msg, RedisConnectionInfo};
6+
use crate::connection::{
7+
resp2_is_pub_sub_state_cleared, resp3_is_pub_sub_state_cleared, ConnectionAddr, ConnectionInfo,
8+
Msg, RedisConnectionInfo,
9+
};
710
#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))]
811
use crate::parser::ValueCodec;
912
use crate::types::{ErrorKind, FromRedisValue, RedisError, RedisFuture, RedisResult, Value};
@@ -36,6 +39,9 @@ pub struct Connection<C = Pin<Box<dyn AsyncStream + Send + Sync>>> {
3639
// This flag is checked when attempting to send a command, and if it's raised, we attempt to
3740
// exit the pubsub state before executing the new request.
3841
pubsub: bool,
42+
43+
// Flag indicating whether resp3 mode is enabled.
44+
resp3: bool,
3945
}
4046

4147
fn assert_sync<T: Sync>() {}
@@ -53,13 +59,15 @@ impl<C> Connection<C> {
5359
decoder,
5460
db,
5561
pubsub,
62+
resp3,
5663
} = self;
5764
Connection {
5865
con: f(con),
5966
buf,
6067
decoder,
6168
db,
6269
pubsub,
70+
resp3,
6371
}
6472
}
6573
}
@@ -77,6 +85,7 @@ where
7785
decoder: combine::stream::Decoder::new(),
7886
db: connection_info.db,
7987
pubsub: false,
88+
resp3: connection_info.use_resp3,
8089
};
8190
setup_connection(connection_info, &mut rv).await?;
8291
Ok(rv)
@@ -143,17 +152,32 @@ where
143152
// messages are received until the _subscription count_ in the responses reach zero.
144153
let mut received_unsub = false;
145154
let mut received_punsub = false;
146-
loop {
147-
let res: (Vec<u8>, (), isize) = from_redis_value(&self.read_response().await?)?;
148-
149-
match res.0.first() {
150-
Some(&b'u') => received_unsub = true,
151-
Some(&b'p') => received_punsub = true,
152-
_ => (),
155+
if self.resp3 {
156+
while let Value::Push { kind, data } = from_redis_value(&self.read_response().await?)? {
157+
if data.len() >= 2 {
158+
if let Value::Int(num) = data[1] {
159+
if resp3_is_pub_sub_state_cleared(
160+
&mut received_unsub,
161+
&mut received_punsub,
162+
&kind,
163+
num as isize,
164+
) {
165+
break;
166+
}
167+
}
168+
}
153169
}
154-
155-
if received_unsub && received_punsub && res.2 == 0 {
156-
break;
170+
} else {
171+
loop {
172+
let res: (Vec<u8>, (), isize) = from_redis_value(&self.read_response().await?)?;
173+
if resp2_is_pub_sub_state_cleared(
174+
&mut received_unsub,
175+
&mut received_punsub,
176+
&res.0,
177+
res.2,
178+
) {
179+
break;
180+
}
157181
}
158182
}
159183

@@ -199,7 +223,17 @@ where
199223
self.buf.clear();
200224
cmd.write_packed_command(&mut self.buf);
201225
self.con.write_all(&self.buf).await?;
202-
self.read_response().await
226+
if cmd.is_no_response() {
227+
return Ok(Value::Nil);
228+
}
229+
loop {
230+
match self.read_response().await? {
231+
Value::Push { .. } => {
232+
//self.execute_push_message(kind, data) //TODO
233+
}
234+
val => return Ok(val),
235+
}
236+
}
203237
})
204238
.boxed()
205239
}
@@ -231,18 +265,28 @@ where
231265
}
232266

233267
let mut rv = Vec::with_capacity(count);
234-
for _ in 0..count {
268+
let mut count = count;
269+
let mut idx = 0;
270+
while idx < count {
235271
let response = self.read_response().await;
236272
match response {
237273
Ok(item) => {
238-
rv.push(item);
274+
// RESP3 can insert push data between command replies
275+
if let Value::Push { .. } = item {
276+
// if that is the case we have to extend the loop and handle push data
277+
count += 1;
278+
// self.execute_push_message(kind, data); //TODO
279+
} else {
280+
rv.push(item);
281+
}
239282
}
240283
Err(err) => {
241284
if first_err.is_none() {
242285
first_err = Some(err);
243286
}
244287
}
245288
}
289+
idx += 1;
246290
}
247291

248292
if let Some(err) = first_err {
@@ -275,31 +319,42 @@ where
275319

276320
/// Subscribes to a new channel.
277321
pub async fn subscribe<T: ToRedisArgs>(&mut self, channel: T) -> RedisResult<()> {
278-
cmd("SUBSCRIBE").arg(channel).query_async(&mut self.0).await
322+
let mut cmd = cmd("SUBSCRIBE");
323+
cmd.arg(channel);
324+
if self.0.resp3 {
325+
cmd.set_no_response(true);
326+
}
327+
cmd.query_async(&mut self.0).await
279328
}
280329

281330
/// Subscribes to a new channel with a pattern.
282331
pub async fn psubscribe<T: ToRedisArgs>(&mut self, pchannel: T) -> RedisResult<()> {
283-
cmd("PSUBSCRIBE")
284-
.arg(pchannel)
285-
.query_async(&mut self.0)
286-
.await
332+
let mut cmd = cmd("PSUBSCRIBE");
333+
cmd.arg(pchannel);
334+
if self.0.resp3 {
335+
cmd.set_no_response(true);
336+
}
337+
cmd.query_async(&mut self.0).await
287338
}
288339

289340
/// Unsubscribes from a channel.
290341
pub async fn unsubscribe<T: ToRedisArgs>(&mut self, channel: T) -> RedisResult<()> {
291-
cmd("UNSUBSCRIBE")
292-
.arg(channel)
293-
.query_async(&mut self.0)
294-
.await
342+
let mut cmd = cmd("UNSUBSCRIBE");
343+
cmd.arg(channel);
344+
if self.0.resp3 {
345+
cmd.set_no_response(true);
346+
}
347+
cmd.query_async(&mut self.0).await
295348
}
296349

297350
/// Unsubscribes from a channel with a pattern.
298351
pub async fn punsubscribe<T: ToRedisArgs>(&mut self, pchannel: T) -> RedisResult<()> {
299-
cmd("PUNSUBSCRIBE")
300-
.arg(pchannel)
301-
.query_async(&mut self.0)
302-
.await
352+
let mut cmd = cmd("PUNSUBSCRIBE");
353+
cmd.arg(pchannel);
354+
if self.0.resp3 {
355+
cmd.set_no_response(true);
356+
}
357+
cmd.query_async(&mut self.0).await
303358
}
304359

305360
/// Returns [`Stream`] of [`Msg`]s from this [`PubSub`]s subscriptions.

0 commit comments

Comments
 (0)