use crate::create_enrichment_strategy::{dict_key, set};
use crate::default_write_enrichment_strategy::{related_ids, required};
use crate::delete_enrichment_strategy::{children_where, link_where, load, predicate};
use crate::enrichment::Enrichment;
use crate::entity::Entity;
use crate::repository::ICRUDRepository;
use crate::result::Result;
use crate::update_enrichment_strategy::UpdateEnrichmentStrategy;
use crate::value::Value;
use crate::{Operator, Where};
use async_trait::async_trait;
use futures_util::TryStreamExt;
use std::collections::HashSet;
use std::sync::Arc;
pub struct DefaultUpdateEnrichmentStrategy;
#[async_trait]
impl<T: Entity> UpdateEnrichmentStrategy<T> for DefaultUpdateEnrichmentStrategy {
async fn update_children(
&self,
writer: Arc<dyn ICRUDRepository>,
enrichment: &Enrichment,
parent: &mut T,
) -> Result<()> {
update_children(writer.as_ref(), enrichment, parent).await
}
async fn update_linked(
&self,
writer: Arc<dyn ICRUDRepository>,
enrichment: &Enrichment,
parent: &mut T,
) -> Result<()> {
update_linked(writer.as_ref(), enrichment, parent).await
}
}
pub(crate) async fn update_children<T: Entity, TChild: Entity>(
writer: &dyn ICRUDRepository<TChild>,
enrichment: &Enrichment,
parent: &mut T,
) -> Result<()> {
let existing = load(writer, enrichment, parent).await?;
let schema = writer.schema();
let mut related = crate::create_enrichment_strategy::children::<T, TChild>(enrichment, parent, schema)?;
if enrichment.r#type == crate::EnrichmentPropertyType::Dictionary {
let key_name = enrichment.map_key()?;
related.sort_by(|left, right| dict_key(left, key_name).cmp(&dict_key(right, key_name)));
}
if let Some(key) = parent.cell(&enrichment.source_property) {
for child in &mut related {
set(child, &enrichment.related_key, key.clone())?;
}
}
let ids = writer.id_names();
let keep: HashSet<String> = related.iter().filter_map(|child| child_key(child, &ids)).collect();
let clause = children_where(writer, enrichment, parent);
let by_parent = predicate(&clause);
for child in existing.iter().filter(|child| {
child_key::<TChild>(*child, &ids)
.map(|key| !keep.contains(&key))
.unwrap_or(true)
}) {
writer.delete(child.clone(), by_parent).await?;
}
for child in &related {
match child_key(child, &ids) {
Some(key)
if existing
.iter()
.any(|stored| child_key(stored, &ids) == Some(key.clone())) =>
{
writer.update(child.clone(), None).await?;
}
_ => {
writer.create(child.clone()).await?;
}
}
}
Ok(())
}
pub(crate) async fn update_linked<T: Entity, TLink: Entity>(
writer: &dyn ICRUDRepository<TLink>,
enrichment: &Enrichment,
parent: &mut T,
) -> Result<()> {
let association = enrichment
.association
.as_ref()
.ok_or_else(|| crate::DatasourceError::message("Missing association"))?;
let parent_key = required(
parent,
&enrichment.source_property,
&enrichment.destination_property,
)?;
let existing = writer
.read_where(&Where::new(Operator::equals(
association.parent.clone(),
parent_key.clone(),
)), None)
.try_collect::<Vec<_>>()
.await?;
let wanted = related_ids(parent, enrichment)?;
let keep: HashSet<String> = wanted.iter().map(Value::wire_text).collect();
for row in &existing {
let id = row.cell(&association.related);
if id
.as_ref()
.map(Value::wire_text)
.is_some_and(|key| keep.contains(&key))
{
continue;
}
let related = id.unwrap_or(Value::Null);
let clause = link_where(association, &parent_key, &related);
writer.delete(row.clone(), Some(&clause)).await?;
}
for id in wanted {
if existing
.iter()
.any(|row| row.cell(&association.related).map(|value| value.wire_text()) == Some(id.wire_text()))
{
continue;
}
let mut row = TLink::default();
set(&mut row, &association.parent, parent_key.clone())?;
set(&mut row, &association.related, id)?;
writer.create(row).await?;
}
Ok(())
}
fn child_key<TChild: Entity>(child: &TChild, ids: &[String]) -> Option<String> {
if ids.is_empty()
|| ids.iter().any(|name| match child.cell(name) {
Some(value) => matches!(value, Value::Null | Value::Int(0))
|| matches!(&value, Value::Text(text) if text.is_empty()),
None => true,
})
{
return None;
}
Some(
ids.iter()
.map(|name| child.cell(name).map(|value| value.wire_text()).unwrap_or_default())
.collect::<Vec<_>>()
.join("\u{1f}"),
)
}