use crate::{FalkorResult, FalkorValue, GraphSchema, Row};
use std::collections::VecDeque;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
pub struct RowStream {
rows: VecDeque<FalkorResult<Row>>,
}
impl RowStream {
#[cfg_attr(
feature = "tracing",
tracing::instrument(name = "Parse Row Stream", skip_all, level = "debug")
)]
pub(crate) fn parse(
header: Arc<[String]>,
raw_rows: Vec<redis::Value>,
graph_schema: &mut GraphSchema,
) -> Self {
Self {
rows: crate::response::row::parse_rows(header, raw_rows, graph_schema).into(),
}
}
pub fn len(&self) -> usize {
self.rows.len()
}
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}
pub fn into_values_lossy(self) -> impl Iterator<Item = Vec<FalkorValue>> {
self.rows.into_iter().map(|row| match row {
Ok(row) => row.into_values(),
Err(err) => vec![FalkorValue::Unparseable(err.to_string())],
})
}
}
impl futures_core::Stream for RowStream {
type Item = FalkorResult<Row>;
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
Poll::Ready(self.get_mut().rows.pop_front())
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.rows.len(), Some(self.rows.len()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::client::blocking::create_empty_inner_sync_client;
use crate::FalkorDBError;
fn scalar_row(n: i64) -> redis::Value {
redis::Value::Array(vec![redis::Value::Array(vec![
redis::Value::Int(3),
redis::Value::Int(n),
])])
}
fn stream_of(values: &[i64]) -> RowStream {
let header: Arc<[String]> = Arc::from(vec!["n".to_string()]);
let mut schema = GraphSchema::new("test", create_empty_inner_sync_client());
RowStream::parse(
header,
values.iter().map(|&n| scalar_row(n)).collect(),
&mut schema,
)
}
fn assert_send_static<T: Send + 'static>() {}
#[test]
fn row_stream_is_send_and_static() {
assert_send_static::<RowStream>();
}
#[test]
fn len_and_is_empty_track_remaining_rows() {
let stream = stream_of(&[42, 7]);
assert_eq!(stream.len(), 2);
assert!(!stream.is_empty());
assert!(stream_of(&[]).is_empty());
}
#[test]
fn stream_try_collect_parses_each_row() {
use futures::TryStreamExt;
let rows: Vec<Row> =
futures::executor::block_on(stream_of(&[42, 7]).try_collect()).unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].try_get_at::<i64>(0).unwrap(), 42);
assert_eq!(rows[1].try_get_at::<i64>(0).unwrap(), 7);
}
#[test]
fn stream_next_via_streamext() {
use futures::StreamExt;
let mut stream = stream_of(&[1, 2]);
let row = futures::executor::block_on(stream.next()).unwrap().unwrap();
assert_eq!(row.try_get_at::<i64>(0).unwrap(), 1);
assert_eq!(stream.len(), 1);
}
#[test]
fn into_values_lossy_yields_bare_rows() {
let rows: Vec<Vec<FalkorValue>> = stream_of(&[5]).into_values_lossy().collect();
assert_eq!(rows, vec![vec![FalkorValue::I64(5)]]);
}
#[test]
fn shape_mismatch_surfaces_as_error() {
let two_value_row = redis::Value::Array(vec![
redis::Value::Array(vec![redis::Value::Int(3), redis::Value::Int(1)]),
redis::Value::Array(vec![redis::Value::Int(3), redis::Value::Int(2)]),
]);
let header: Arc<[String]> = Arc::from(vec!["n".to_string()]);
let mut schema = GraphSchema::new("test", create_empty_inner_sync_client());
let mut stream = RowStream::parse(header, vec![two_value_row], &mut schema);
let err = stream.rows.pop_front().unwrap().unwrap_err();
assert!(matches!(
err,
FalkorDBError::RowShapeMismatch {
header_len: 1,
value_len: 2,
}
));
}
#[test]
fn into_values_lossy_collapses_parse_errors() {
let bad_row = redis::Value::Array(vec![redis::Value::Array(vec![
redis::Value::Int(9999),
redis::Value::Int(1),
])]);
let header: Arc<[String]> = Arc::from(vec!["n".to_string()]);
let mut schema = GraphSchema::new("test", create_empty_inner_sync_client());
let stream = RowStream::parse(header, vec![bad_row], &mut schema);
let rows: Vec<Vec<FalkorValue>> = stream.into_values_lossy().collect();
assert_eq!(rows.len(), 1);
assert!(matches!(rows[0].as_slice(), [FalkorValue::Unparseable(_)]));
}
#[test]
fn size_hint_reports_remaining_rows() {
use futures_core::Stream;
let stream = stream_of(&[1, 2, 3]);
assert_eq!(Stream::size_hint(&stream), (3, Some(3)));
}
}