use crate::{FalkorDBError, FalkorResult, FalkorValue, FromFalkorValue};
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Clone, Debug, PartialEq)]
pub struct Row {
header: Arc<[String]>,
values: Vec<FalkorValue>,
}
impl Row {
pub(crate) fn new(
header: Arc<[String]>,
values: Vec<FalkorValue>,
) -> Self {
Self { header, values }
}
fn index_of(
&self,
column: &str,
) -> Option<usize> {
self.header.iter().position(|name| name == column)
}
pub fn columns(&self) -> &[String] {
&self.header
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn get(
&self,
column: &str,
) -> Option<&FalkorValue> {
self.index_of(column).map(|index| &self.values[index])
}
pub fn get_at(
&self,
index: usize,
) -> Option<&FalkorValue> {
self.values.get(index)
}
pub fn get_all<'s>(
&'s self,
column: &'s str,
) -> impl Iterator<Item = &'s FalkorValue> + 's {
self.header
.iter()
.zip(&self.values)
.filter_map(move |(name, value)| (name == column).then_some(value))
}
pub fn try_get<T: FromFalkorValue>(
&self,
column: &str,
) -> FalkorResult<T> {
let index = self
.index_of(column)
.ok_or_else(|| FalkorDBError::MissingColumn {
name: column.to_string(),
})?;
T::from_falkor_value(self.values[index].clone())
}
pub fn try_get_at<T: FromFalkorValue>(
&self,
index: usize,
) -> FalkorResult<T> {
let value = self
.values
.get(index)
.ok_or(FalkorDBError::ColumnIndexOutOfBounds {
index,
len: self.values.len(),
})?;
T::from_falkor_value(value.clone())
}
pub fn into_values(self) -> Vec<FalkorValue> {
self.values
}
pub fn into_map(self) -> HashMap<String, FalkorValue> {
self.header.iter().cloned().zip(self.values).collect()
}
#[cfg(feature = "serde")]
pub fn deserialize<T: serde::de::DeserializeOwned>(self) -> FalkorResult<T> {
crate::from_falkor_row(&self.header, self.values)
}
}
impl IntoIterator for Row {
type Item = FalkorValue;
type IntoIter = std::vec::IntoIter<FalkorValue>;
fn into_iter(self) -> Self::IntoIter {
self.values.into_iter()
}
}
impl FromIterator<(String, FalkorValue)> for Row {
fn from_iter<I: IntoIterator<Item = (String, FalkorValue)>>(iter: I) -> Self {
let (header, values): (Vec<String>, Vec<FalkorValue>) = iter.into_iter().unzip();
Self {
header: header.into(),
values,
}
}
}
pub(crate) fn parse_rows(
header: Arc<[String]>,
raw_rows: Vec<redis::Value>,
graph_schema: &mut crate::GraphSchema,
) -> Vec<FalkorResult<Row>> {
use crate::parser::{parse_type, ParserTypeMarker};
raw_rows
.into_iter()
.map(|raw| {
let values = parse_type(ParserTypeMarker::Array, raw, graph_schema)
.and_then(FalkorValue::into_vec)?;
if values.len() != header.len() {
return Err(FalkorDBError::RowShapeMismatch {
header_len: header.len(),
value_len: values.len(),
});
}
Ok(Row::new(Arc::clone(&header), values))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> Row {
Row::from_iter([
("title".to_string(), FalkorValue::from("Dune")),
("year".to_string(), FalkorValue::I64(1965)),
("rating".to_string(), FalkorValue::F64(8.2)),
("sequel".to_string(), FalkorValue::None),
])
}
#[test]
fn columns_len_is_empty() {
let row = sample();
assert_eq!(row.columns(), ["title", "year", "rating", "sequel"]);
assert_eq!(row.len(), 4);
assert!(!row.is_empty());
let empty = Row::from_iter([]);
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
}
#[test]
fn get_and_get_at() {
let row = sample();
assert_eq!(row.get("year"), Some(&FalkorValue::I64(1965)));
assert_eq!(row.get("missing"), None);
assert_eq!(row.get_at(0), Some(&FalkorValue::from("Dune")));
assert_eq!(row.get_at(99), None);
}
#[test]
fn try_get_typed() {
let row = sample();
assert_eq!(row.try_get::<String>("title").unwrap(), "Dune");
assert_eq!(row.try_get::<i64>("year").unwrap(), 1965);
assert_eq!(row.try_get::<f64>("rating").unwrap(), 8.2);
assert_eq!(row.try_get::<Option<String>>("sequel").unwrap(), None);
}
#[test]
fn try_get_missing_vs_null() {
let row = sample();
assert_eq!(
row.try_get::<Option<String>>("nope"),
Err(FalkorDBError::MissingColumn {
name: "nope".to_string()
})
);
assert_eq!(row.try_get::<Option<String>>("sequel").unwrap(), None);
}
#[test]
fn try_get_type_error() {
let row = sample();
assert_eq!(
row.try_get::<i64>("title"),
Err(FalkorDBError::TypeError {
expected: "i64",
got: "String"
})
);
}
#[test]
fn try_get_at_out_of_bounds() {
let row = sample();
assert_eq!(
row.try_get_at::<i64>(10),
Err(FalkorDBError::ColumnIndexOutOfBounds { index: 10, len: 4 })
);
}
#[test]
fn duplicate_columns_first_wins_and_get_all() {
let row = Row::from_iter([
("x".to_string(), FalkorValue::I64(1)),
("x".to_string(), FalkorValue::I64(2)),
("y".to_string(), FalkorValue::I64(3)),
]);
assert_eq!(row.get("x"), Some(&FalkorValue::I64(1)));
assert_eq!(row.try_get::<i64>("x").unwrap(), 1);
let all: Vec<&FalkorValue> = row.get_all("x").collect();
assert_eq!(all, vec![&FalkorValue::I64(1), &FalkorValue::I64(2)]);
let map = row.into_map();
assert_eq!(map.get("x"), Some(&FalkorValue::I64(2)));
}
#[test]
fn into_values_and_iter() {
let row = sample();
let collected: Vec<FalkorValue> = row.clone().into_iter().collect();
assert_eq!(collected, row.into_values());
}
}