use super::{RedisConnection, RedisStreamEvent, RedisStreamRecord};
use crate::burst;
use crate::nodes::{FutStream, RunParams, StreamOperators, produce_async};
use crate::types::*;
use futures::StreamExt;
use redis::AsyncCommands;
use redis::streams::{StreamId, StreamRangeReply, StreamReadOptions, StreamReadReply};
use std::pin::Pin;
use std::rc::Rc;
fn to_event(key: &str, id: &StreamId) -> RedisStreamEvent {
let fields = id
.map
.iter()
.filter_map(|(name, value)| {
redis::from_redis_value::<Vec<u8>>(value)
.ok()
.map(|bytes| (name.clone(), bytes))
})
.collect();
RedisStreamEvent {
key: key.to_string(),
id: id.id.clone(),
fields,
}
}
#[must_use]
pub fn redis_stream_read(
connection: impl Into<RedisConnection>,
key: impl Into<String>,
) -> Rc<dyn Stream<Burst<RedisStreamEvent>>> {
let connection = connection.into();
let key = key.into();
produce_async(
move |_ctx: RunParams| async move {
let client = redis::Client::open(connection.url.as_str())
.map_err(|e| anyhow::anyhow!("redis client open failed: {e}"))?;
let mut conn = client
.get_multiplexed_async_connection()
.await
.map_err(|e| anyhow::anyhow!("redis connect failed: {e}"))?;
let snapshot: StreamRangeReply = conn
.xrange(&key, "-", "+")
.await
.map_err(|e| anyhow::anyhow!("redis xrange on {key:?} failed: {e}"))?;
let mut last_id = snapshot
.ids
.last()
.map(|id| id.id.clone())
.unwrap_or_else(|| "0".to_string());
let snapshot_events: Vec<RedisStreamEvent> =
snapshot.ids.iter().map(|id| to_event(&key, id)).collect();
Ok(async_stream::stream! {
for event in snapshot_events {
yield Ok((NanoTime::now(), event));
}
let opts = StreamReadOptions::default().block(0);
loop {
let reply: redis::RedisResult<StreamReadReply> =
conn.xread_options(&[&key], &[&last_id], &opts).await;
match reply {
Ok(reply) => {
for stream_key in &reply.keys {
for id in &stream_key.ids {
last_id = id.id.clone();
yield Ok((NanoTime::now(), to_event(&stream_key.key, id)));
}
}
}
Err(e) => {
yield Err(anyhow::anyhow!("redis xread on {key:?} failed: {e}"));
break;
}
}
}
})
},
None,
)
}
#[must_use]
pub fn redis_stream_write(
connection: impl Into<RedisConnection>,
upstream: &Rc<dyn Stream<Burst<RedisStreamRecord>>>,
) -> Rc<dyn Node> {
let connection = connection.into();
upstream.consume_async(Box::new(
move |_ctx: RunParams,
mut source: Pin<Box<dyn FutStream<Burst<RedisStreamRecord>>>>| async move {
let client = redis::Client::open(connection.url.as_str())
.map_err(|e| anyhow::anyhow!("redis client open failed: {e}"))?;
let mut conn = client
.get_multiplexed_async_connection()
.await
.map_err(|e| anyhow::anyhow!("redis connect failed: {e}"))?;
while let Some((_time, burst)) = source.next().await {
for record in burst {
let mut cmd = redis::cmd("XADD");
cmd.arg(record.key.as_str()).arg("*");
for (field, value) in &record.fields {
cmd.arg(field.as_str()).arg(value.as_slice());
}
cmd.query_async::<String>(&mut conn).await.map_err(|e| {
anyhow::anyhow!("redis xadd to {:?} failed: {e}", record.key)
})?;
}
}
Ok(())
},
))
}
pub trait RedisStreamOperators {
#[must_use]
fn redis_stream_write(self: &Rc<Self>, conn: impl Into<RedisConnection>) -> Rc<dyn Node>;
}
impl RedisStreamOperators for dyn Stream<Burst<RedisStreamRecord>> {
fn redis_stream_write(self: &Rc<Self>, conn: impl Into<RedisConnection>) -> Rc<dyn Node> {
redis_stream_write(conn, self)
}
}
impl RedisStreamOperators for dyn Stream<RedisStreamRecord> {
fn redis_stream_write(self: &Rc<Self>, conn: impl Into<RedisConnection>) -> Rc<dyn Node> {
let burst_stream = self.map(|record| burst![record]);
redis_stream_write(conn, &burst_stream)
}
}