use std::{
collections::HashMap,
sync::{Arc, PoisonError, RwLock},
};
use serde_json::Value;
use super::{result::ComposedTags, tviews_feed::Collection};
use crate::{
backend::{
COMPOSED_DOCUMENT_KEY, COMPOSED_EMBEDS_KEY, ComposedEmbed, ComposedLevel, EmbedShape,
EmbedSource, NativeCorrelation,
},
db::types::JsonbValue,
};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CollectionMap {
levels: HashMap<(String, String, String, String), Collection>,
}
impl CollectionMap {
#[must_use]
pub fn from_levels<'a>(
levels: impl IntoIterator<Item = ((&'a str, &'a str, &'a str, &'a str), Collection)>,
) -> Self {
Self {
levels: levels
.into_iter()
.map(|((child, parent, column, key), c)| {
((child.into(), parent.into(), column.into(), key.into()), c)
})
.collect(),
}
}
#[must_use]
pub fn collection(
&self,
child_view: &str,
parent_view: &str,
join: &NativeCorrelation,
) -> Option<&Collection> {
self.levels.get(&(
child_view.to_string(),
parent_view.to_string(),
join.target_column.clone(),
join.parent_column.clone(),
))
}
}
#[derive(Debug, Clone, Default)]
pub struct CollectionTagging(Arc<RwLock<Option<Arc<CollectionMap>>>>);
impl CollectionTagging {
#[must_use]
pub fn current(&self) -> Option<Arc<CollectionMap>> {
self.0.read().unwrap_or_else(PoisonError::into_inner).clone()
}
pub fn hear(&self, map: CollectionMap) {
*self.0.write().unwrap_or_else(PoisonError::into_inner) = Some(Arc::new(map));
}
pub fn deafen(&self) {
*self.0.write().unwrap_or_else(PoisonError::into_inner) = None;
}
}
#[must_use]
pub fn composed_tags(
read: &ComposedLevel,
rows: &[JsonbValue],
map: Option<&CollectionMap>,
views_for: impl Fn(&str) -> Vec<String>,
) -> (Vec<String>, ComposedTags) {
let mut walk = Walk {
map,
views_for: &views_for,
views: views_for(&read.view),
tags: ComposedTags::default(),
};
let rows: Vec<&Value> = rows.iter().map(JsonbValue::as_value).collect();
walk.level(read, &rows);
let Walk {
mut views,
mut tags,
..
} = walk;
dedup(&mut views);
dedup(&mut tags.collected_views);
(views, tags)
}
struct Walk<'a, F> {
map: Option<&'a CollectionMap>,
views_for: &'a F,
views: Vec<String>,
tags: ComposedTags,
}
impl<F: Fn(&str) -> Vec<String>> Walk<'_, F> {
fn level(&mut self, level: &ComposedLevel, rows: &[&Value]) {
if let Some(entity_type) = super::view_name_to_entity_type(&level.view) {
self.tags.entities.extend(
rows.iter().filter_map(|row| row_id(row)).map(|id| (entity_type.clone(), id)),
);
}
for embed in &level.embeds {
if let EmbedSource::Correlated { .. } = embed.source {
match self.collection(level, embed, rows) {
Some(members) => {
self.tags.collected_views.extend((self.views_for)(&embed.level.view));
self.tags.collections.extend(members);
},
None => self.views.extend((self.views_for)(&embed.level.view)),
}
}
if embed.shape != EmbedShape::Count {
let children: Vec<&Value> = rows
.iter()
.filter_map(|row| row.get(COMPOSED_EMBEDS_KEY)?.get(&embed.output_key))
.flat_map(|value| match value {
Value::Array(items) => items.iter().collect(),
Value::Object(_) => vec![value],
_ => Vec::new(),
})
.collect();
self.level(&embed.level, &children);
}
}
}
fn collection(
&self,
parent: &ComposedLevel,
embed: &ComposedEmbed,
rows: &[&Value],
) -> Option<Vec<(Collection, String)>> {
let EmbedSource::Correlated {
native: Some(join), ..
} = &embed.source
else {
return None;
};
if embed.shape == EmbedShape::One {
return None;
}
let collection = self.map?.collection(&embed.level.view, &parent.view, join)?;
rows.iter().map(|row| Some((collection.clone(), row_id(row)?))).collect()
}
}
fn row_id(row: &Value) -> Option<String> {
super::tviews_feed::id_text(row.get(COMPOSED_DOCUMENT_KEY)?.get("id")?)
}
fn dedup(names: &mut Vec<String>) {
let mut seen = std::collections::HashSet::new();
names.retain(|name| seen.insert(name.clone()));
}
#[cfg(test)]
#[path = "collections_tests.rs"]
mod collections_tests;