use std::collections::{HashMap, HashSet};
use petgraph::Direction;
use crate::datatypes::values::Value;
use crate::datatypes::DataFrame;
use crate::graph::dir_graph::DirGraph;
use crate::graph::storage::interner::InternedKey;
use crate::graph::storage::GraphRead;
use super::batch::{sum_values, ConflictHandling};
pub(crate) struct ConnectionBatchGate<'a> {
pub connection_type: &'a str,
pub df_data: &'a DataFrame,
pub property_columns: &'a [(String, InternedKey, usize)],
pub matched: &'a [(
usize,
petgraph::graph::NodeIndex,
petgraph::graph::NodeIndex,
)],
pub deferred: &'a [(usize, Value, Value)],
pub conflict_mode: ConflictHandling,
pub folding: RowFolding,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RowFolding {
Independent,
Merging { read_stored: bool },
}
impl RowFolding {
pub(crate) fn for_load(skip_existence_check: bool) -> Self {
if skip_existence_check {
RowFolding::Independent
} else {
RowFolding::Merging { read_stored: true }
}
}
pub(crate) fn for_replace(graph: &DirGraph, connection_type: &str) -> Self {
if graph.connection_type_metadata.contains_key(connection_type) {
RowFolding::Merging { read_stored: false }
} else {
RowFolding::Independent
}
}
}
type PairState = Vec<Option<Value>>;
impl ConnectionBatchGate<'_> {
pub(crate) fn run(self, graph: &mut DirGraph) -> Result<(), String> {
if !graph.has_rel_constraints() || !graph.type_has_rel_constraints(self.connection_type) {
return Ok(());
}
let names = graph.rel_constrained_properties(self.connection_type);
let columns: Vec<Option<usize>> = names
.iter()
.map(|name| {
self.property_columns
.iter()
.find(|(column, _, _)| column == name)
.map(|(_, _, index)| *index)
})
.collect();
if self.folding == RowFolding::Independent {
for (row_idx, ..) in self.matched {
let row = self.row_values(*row_idx, &columns);
self.verdict(graph, &names, &row)?;
}
for (row_idx, ..) in self.deferred {
let row = self.row_values(*row_idx, &columns);
self.verdict(graph, &names, &row)?;
}
return Ok(());
}
let stored = self.stored_state(graph, &names);
let mut matched_state: HashMap<(usize, usize), PairState> = HashMap::new();
let mut deferred_state: HashMap<(Value, Value), PairState> = HashMap::new();
for (row_idx, source, target) in self.matched {
let key = (source.index(), target.index());
let existing = stored.get(&key);
let state = match matched_state.get(&key) {
Some(state) => state.clone(),
None => existing.cloned().unwrap_or_else(|| vec![None; names.len()]),
};
let already_there = existing.is_some() || matched_state.contains_key(&key);
let merged = self.merge_row(*row_idx, &columns, state, already_there);
self.verdict(graph, &names, &merged)?;
matched_state.insert(key, merged);
}
for (row_idx, source_id, target_id) in self.deferred {
let key = (source_id.clone(), target_id.clone());
let state = deferred_state
.get(&key)
.cloned()
.unwrap_or_else(|| vec![None; names.len()]);
let already_there = deferred_state.contains_key(&key);
let merged = self.merge_row(*row_idx, &columns, state, already_there);
self.verdict(graph, &names, &merged)?;
deferred_state.insert(key, merged);
}
Ok(())
}
fn stored_state(
&self,
graph: &DirGraph,
names: &[String],
) -> HashMap<(usize, usize), PairState> {
let mut stored: HashMap<(usize, usize), PairState> = HashMap::new();
if self.folding != (RowFolding::Merging { read_stored: true }) {
return stored;
}
let conn_key = InternedKey::from_str(self.connection_type);
let keys: Vec<InternedKey> = names
.iter()
.map(|name| InternedKey::from_str(name))
.collect();
let sources: HashSet<petgraph::graph::NodeIndex> =
self.matched.iter().map(|(_, source, _)| *source).collect();
for source in sources {
for edge in graph.graph.edges_directed(source, Direction::Outgoing) {
let weight = edge.weight();
if weight.connection_type != conn_key {
continue;
}
let values = keys
.iter()
.map(|key| {
weight
.properties
.iter()
.find(|(stored_key, _)| stored_key == key)
.map(|(_, value)| value.clone())
.filter(|value| !matches!(value, Value::Null))
})
.collect();
stored.insert((source.index(), edge.target().index()), values);
}
}
stored
}
fn merge_row(
&self,
row_idx: usize,
columns: &[Option<usize>],
state: PairState,
already_there: bool,
) -> PairState {
let row = self.row_values(row_idx, columns);
if !already_there {
return row;
}
match self.conflict_mode {
ConflictHandling::Skip => state,
ConflictHandling::Replace => row,
ConflictHandling::Update => state
.into_iter()
.zip(row)
.map(|(stored, incoming)| incoming.or(stored))
.collect(),
ConflictHandling::Preserve => state
.into_iter()
.zip(row)
.map(|(stored, incoming)| stored.or(incoming))
.collect(),
ConflictHandling::Sum => state
.into_iter()
.zip(row)
.map(|(stored, incoming)| match (stored, incoming) {
(Some(stored), Some(incoming)) => Some(sum_values(&stored, &incoming)),
(stored, incoming) => incoming.or(stored),
})
.collect(),
}
}
fn row_values(&self, row_idx: usize, columns: &[Option<usize>]) -> PairState {
columns
.iter()
.map(|column| {
column
.and_then(|index| self.df_data.get_value_by_index(row_idx, index))
.filter(|value| !matches!(value, Value::Null))
})
.collect()
}
fn verdict(
&self,
graph: &mut DirGraph,
names: &[String],
state: &PairState,
) -> Result<(), String> {
graph.check_rel_row(self.connection_type, |property| {
names
.iter()
.position(|name| name == property)
.and_then(|index| state[index].clone())
})
}
}
#[cfg(test)]
#[path = "rel_constraint_gate_tests.rs"]
mod tests;