1use std::collections::HashMap;
8
9use crate::graph::graph_classifier::{GraphClass, GraphClassifier, GraphValue};
10use crate::graph::graph_query::GraphQuery;
11use crate::pattern::Pattern;
12use crate::reconcile::{HasIdentity, Mergeable, ReconciliationPolicy, Refinable};
13use crate::subject::Symbol;
14
15pub struct PatternGraph<Extra, V: GraphValue> {
21 pub pg_nodes: HashMap<V::Id, Pattern<V>>,
22 pub pg_relationships: HashMap<V::Id, Pattern<V>>,
23 pub pg_walks: HashMap<V::Id, Pattern<V>>,
24 pub pg_annotations: HashMap<V::Id, Pattern<V>>,
25 pub pg_other: HashMap<V::Id, (Extra, Pattern<V>)>,
26 pub pg_conflicts: HashMap<V::Id, Vec<Pattern<V>>>,
27}
28
29impl<Extra, V: GraphValue> PatternGraph<Extra, V> {
30 pub fn empty() -> Self {
32 PatternGraph {
33 pg_nodes: HashMap::new(),
34 pg_relationships: HashMap::new(),
35 pg_walks: HashMap::new(),
36 pg_annotations: HashMap::new(),
37 pg_other: HashMap::new(),
38 pg_conflicts: HashMap::new(),
39 }
40 }
41}
42
43fn two_occurrences<V: Clone>(existing: &Pattern<V>, incoming: Pattern<V>) -> Pattern<V> {
57 Pattern {
58 value: existing.value.clone(),
59 elements: vec![incoming],
60 }
61}
62
63fn insert_node<Extra, V>(
68 policy: &ReconciliationPolicy<V::MergeStrategy>,
69 p: Pattern<V>,
70 mut g: PatternGraph<Extra, V>,
71) -> PatternGraph<Extra, V>
72where
73 V: GraphValue<Id = Symbol> + HasIdentity<V, Symbol> + Mergeable + Refinable + PartialEq + Clone,
74{
75 let i = V::identity(&p.value).clone();
76 match g.pg_nodes.remove(&i) {
77 None => {
78 g.pg_nodes.insert(i, p);
79 }
80 Some(existing) => {
81 let synthetic = two_occurrences(&existing, p.clone());
82 match crate::reconcile::reconcile(policy, &synthetic) {
83 Err(_) => {
84 g.pg_nodes.insert(i.clone(), existing);
85 g.pg_conflicts.entry(i).or_default().push(p);
86 }
87 Ok(merged) => {
88 g.pg_nodes.insert(i, merged);
89 }
90 }
91 }
92 }
93 g
94}
95
96fn insert_relationship<Extra, V>(
97 classifier: &GraphClassifier<Extra, V>,
98 policy: &ReconciliationPolicy<V::MergeStrategy>,
99 p: Pattern<V>,
100 g: PatternGraph<Extra, V>,
101) -> PatternGraph<Extra, V>
102where
103 V: GraphValue<Id = Symbol>
104 + HasIdentity<V, Symbol>
105 + Mergeable
106 + Refinable
107 + PartialEq
108 + Clone
109 + 'static,
110 Extra: 'static,
111{
112 let g1 = if p.elements.len() == 2 {
114 let n1 = p.elements[0].clone();
115 let n2 = p.elements[1].clone();
116 let g1 = merge_with_policy(classifier, policy, n1, g);
117 merge_with_policy(classifier, policy, n2, g1)
118 } else {
119 g
120 };
121
122 let i = V::identity(&p.value).clone();
123 let mut g2 = g1;
124 match g2.pg_relationships.remove(&i) {
125 None => {
126 g2.pg_relationships.insert(i, p);
127 }
128 Some(existing) => {
129 let synthetic = two_occurrences(&existing, p.clone());
130 match crate::reconcile::reconcile(policy, &synthetic) {
131 Err(_) => {
132 g2.pg_relationships.insert(i.clone(), existing);
133 g2.pg_conflicts.entry(i).or_default().push(p);
134 }
135 Ok(merged) => {
136 g2.pg_relationships.insert(i, merged);
137 }
138 }
139 }
140 }
141 g2
142}
143
144fn insert_walk<Extra, V>(
145 classifier: &GraphClassifier<Extra, V>,
146 policy: &ReconciliationPolicy<V::MergeStrategy>,
147 p: Pattern<V>,
148 g: PatternGraph<Extra, V>,
149) -> PatternGraph<Extra, V>
150where
151 V: GraphValue<Id = Symbol>
152 + HasIdentity<V, Symbol>
153 + Mergeable
154 + Refinable
155 + PartialEq
156 + Clone
157 + 'static,
158 Extra: 'static,
159{
160 let elements: Vec<Pattern<V>> = p.elements.clone();
162 let g1 = elements.into_iter().fold(g, |acc, elem| {
163 merge_with_policy(classifier, policy, elem, acc)
164 });
165
166 let i = V::identity(&p.value).clone();
167 let mut g2 = g1;
168 match g2.pg_walks.remove(&i) {
169 None => {
170 g2.pg_walks.insert(i, p);
171 }
172 Some(existing) => {
173 let synthetic = two_occurrences(&existing, p.clone());
174 match crate::reconcile::reconcile(policy, &synthetic) {
175 Err(_) => {
176 g2.pg_walks.insert(i.clone(), existing);
177 g2.pg_conflicts.entry(i).or_default().push(p);
178 }
179 Ok(merged) => {
180 g2.pg_walks.insert(i, merged);
181 }
182 }
183 }
184 }
185 g2
186}
187
188fn insert_annotation<Extra, V>(
189 classifier: &GraphClassifier<Extra, V>,
190 policy: &ReconciliationPolicy<V::MergeStrategy>,
191 p: Pattern<V>,
192 g: PatternGraph<Extra, V>,
193) -> PatternGraph<Extra, V>
194where
195 V: GraphValue<Id = Symbol>
196 + HasIdentity<V, Symbol>
197 + Mergeable
198 + Refinable
199 + PartialEq
200 + Clone
201 + 'static,
202 Extra: 'static,
203{
204 let g1 = if p.elements.len() == 1 {
206 let inner = p.elements[0].clone();
207 merge_with_policy(classifier, policy, inner, g)
208 } else {
209 g
210 };
211
212 let i = V::identity(&p.value).clone();
213 let mut g2 = g1;
214 match g2.pg_annotations.remove(&i) {
215 None => {
216 g2.pg_annotations.insert(i, p);
217 }
218 Some(existing) => {
219 let synthetic = two_occurrences(&existing, p.clone());
220 match crate::reconcile::reconcile(policy, &synthetic) {
221 Err(_) => {
222 g2.pg_annotations.insert(i.clone(), existing);
223 g2.pg_conflicts.entry(i).or_default().push(p);
224 }
225 Ok(merged) => {
226 g2.pg_annotations.insert(i, merged);
227 }
228 }
229 }
230 }
231 g2
232}
233
234fn insert_other<Extra, V>(
235 policy: &ReconciliationPolicy<V::MergeStrategy>,
236 extra: Extra,
237 p: Pattern<V>,
238 mut g: PatternGraph<Extra, V>,
239) -> PatternGraph<Extra, V>
240where
241 V: GraphValue<Id = Symbol> + HasIdentity<V, Symbol> + Mergeable + Refinable + PartialEq + Clone,
242{
243 let i = V::identity(&p.value).clone();
244 match g.pg_other.remove(&i) {
245 None => {
246 g.pg_other.insert(i, (extra, p));
247 }
248 Some((existing_extra, existing)) => {
249 let synthetic = two_occurrences(&existing, p.clone());
250 match crate::reconcile::reconcile(policy, &synthetic) {
251 Err(_) => {
252 g.pg_other.insert(i.clone(), (existing_extra, existing));
253 g.pg_conflicts.entry(i).or_default().push(p);
254 }
255 Ok(merged) => {
256 g.pg_other.insert(i, (existing_extra, merged));
257 }
258 }
259 }
260 }
261 g
262}
263
264pub fn merge_with_policy<Extra, V>(
273 classifier: &GraphClassifier<Extra, V>,
274 policy: &ReconciliationPolicy<V::MergeStrategy>,
275 p: Pattern<V>,
276 g: PatternGraph<Extra, V>,
277) -> PatternGraph<Extra, V>
278where
279 V: GraphValue<Id = Symbol>
280 + HasIdentity<V, Symbol>
281 + Mergeable
282 + Refinable
283 + PartialEq
284 + Clone
285 + 'static,
286 Extra: 'static,
287{
288 match (classifier.classify)(&p) {
289 GraphClass::GNode => insert_node(policy, p, g),
290 GraphClass::GRelationship => insert_relationship(classifier, policy, p, g),
291 GraphClass::GWalk => insert_walk(classifier, policy, p, g),
292 GraphClass::GAnnotation => insert_annotation(classifier, policy, p, g),
293 GraphClass::GOther(extra) => insert_other(policy, extra, p, g),
294 }
295}
296
297pub fn merge<Extra, V>(
299 classifier: &GraphClassifier<Extra, V>,
300 p: Pattern<V>,
301 g: PatternGraph<Extra, V>,
302) -> PatternGraph<Extra, V>
303where
304 V: GraphValue<Id = Symbol>
305 + HasIdentity<V, Symbol>
306 + Mergeable
307 + Refinable
308 + PartialEq
309 + Clone
310 + 'static,
311 Extra: 'static,
312{
313 merge_with_policy(classifier, &ReconciliationPolicy::LastWriteWins, p, g)
314}
315
316pub fn from_patterns<Extra, V>(
318 classifier: &GraphClassifier<Extra, V>,
319 patterns: impl IntoIterator<Item = Pattern<V>>,
320) -> PatternGraph<Extra, V>
321where
322 V: GraphValue<Id = Symbol>
323 + HasIdentity<V, Symbol>
324 + Mergeable
325 + Refinable
326 + PartialEq
327 + Clone
328 + 'static,
329 Extra: 'static,
330{
331 from_patterns_with_policy(classifier, &ReconciliationPolicy::LastWriteWins, patterns)
332}
333
334#[cfg(not(feature = "thread-safe"))]
358pub fn from_pattern_graph<Extra, V>(graph: std::rc::Rc<PatternGraph<Extra, V>>) -> GraphQuery<V>
359where
360 Extra: 'static,
361 V: GraphValue + Clone + 'static,
362 V::Id: Clone + Eq + std::hash::Hash + 'static,
363{
364 use std::rc::Rc;
365
366 let g1 = Rc::clone(&graph);
367 let query_nodes = Rc::new(move || g1.pg_nodes.values().cloned().collect());
368
369 let g2 = Rc::clone(&graph);
370 let query_relationships = Rc::new(move || g2.pg_relationships.values().cloned().collect());
371
372 let g3 = Rc::clone(&graph);
373 let query_incident_rels = Rc::new(move |node: &Pattern<V>| {
374 let node_id = node.value.identify();
375 g3.pg_relationships
376 .values()
377 .filter(|rel| {
378 rel.elements.len() == 2
379 && (rel.elements[0].value.identify() == node_id
380 || rel.elements[1].value.identify() == node_id)
381 })
382 .cloned()
383 .collect()
384 });
385
386 let query_source = Rc::new(|rel: &Pattern<V>| rel.elements.first().cloned());
388 let query_target = Rc::new(|rel: &Pattern<V>| rel.elements.get(1).cloned());
389
390 let g4 = Rc::clone(&graph);
391 let query_degree = Rc::new(move |node: &Pattern<V>| {
392 let node_id = node.value.identify();
393 g4.pg_relationships
394 .values()
395 .filter(|rel| {
396 rel.elements.len() == 2
397 && (rel.elements[0].value.identify() == node_id
398 || rel.elements[1].value.identify() == node_id)
399 })
400 .count()
401 });
402
403 let g5 = Rc::clone(&graph);
404 let query_node_by_id = Rc::new(move |id: &V::Id| g5.pg_nodes.get(id).cloned());
405
406 let g6 = Rc::clone(&graph);
407 let query_relationship_by_id = Rc::new(move |id: &V::Id| g6.pg_relationships.get(id).cloned());
408
409 let g7 = Rc::clone(&graph);
410 let query_containers = Rc::new(move |element: &Pattern<V>| {
411 let elem_id = element.value.identify();
412 let mut containers = Vec::new();
413
414 for rel in g7.pg_relationships.values() {
416 if rel.elements.len() == 2
417 && (rel.elements[0].value.identify() == elem_id
418 || rel.elements[1].value.identify() == elem_id)
419 {
420 containers.push(rel.clone());
421 }
422 }
423
424 for walk in g7.pg_walks.values() {
426 if walk.elements.iter().any(|e| e.value.identify() == elem_id) {
427 containers.push(walk.clone());
428 }
429 }
430
431 for ann in g7.pg_annotations.values() {
433 if ann.elements.len() == 1 && ann.elements[0].value.identify() == elem_id {
434 containers.push(ann.clone());
435 }
436 }
437
438 containers
439 });
440
441 GraphQuery {
442 query_nodes,
443 query_relationships,
444 query_incident_rels,
445 query_source,
446 query_target,
447 query_degree,
448 query_node_by_id,
449 query_relationship_by_id,
450 query_containers,
451 }
452}
453
454#[cfg(feature = "thread-safe")]
455pub fn from_pattern_graph<Extra, V>(graph: std::sync::Arc<PatternGraph<Extra, V>>) -> GraphQuery<V>
456where
457 Extra: Send + Sync + 'static,
458 V: GraphValue + Clone + Send + Sync + 'static,
459 V::Id: Clone + Eq + std::hash::Hash + Send + Sync + 'static,
460{
461 use std::sync::Arc;
462
463 let g1 = Arc::clone(&graph);
464 let query_nodes = Arc::new(move || g1.pg_nodes.values().cloned().collect());
465
466 let g2 = Arc::clone(&graph);
467 let query_relationships = Arc::new(move || g2.pg_relationships.values().cloned().collect());
468
469 let g3 = Arc::clone(&graph);
470 let query_incident_rels = Arc::new(move |node: &Pattern<V>| {
471 let node_id = node.value.identify();
472 g3.pg_relationships
473 .values()
474 .filter(|rel| {
475 rel.elements.len() == 2
476 && (rel.elements[0].value.identify() == node_id
477 || rel.elements[1].value.identify() == node_id)
478 })
479 .cloned()
480 .collect()
481 });
482
483 let query_source = Arc::new(|rel: &Pattern<V>| rel.elements.first().cloned());
484 let query_target = Arc::new(|rel: &Pattern<V>| rel.elements.get(1).cloned());
485
486 let g4 = Arc::clone(&graph);
487 let query_degree = Arc::new(move |node: &Pattern<V>| {
488 let node_id = node.value.identify();
489 g4.pg_relationships
490 .values()
491 .filter(|rel| {
492 rel.elements.len() == 2
493 && (rel.elements[0].value.identify() == node_id
494 || rel.elements[1].value.identify() == node_id)
495 })
496 .count()
497 });
498
499 let g5 = Arc::clone(&graph);
500 let query_node_by_id = Arc::new(move |id: &V::Id| g5.pg_nodes.get(id).cloned());
501
502 let g6 = Arc::clone(&graph);
503 let query_relationship_by_id = Arc::new(move |id: &V::Id| g6.pg_relationships.get(id).cloned());
504
505 let g7 = Arc::clone(&graph);
506 let query_containers = Arc::new(move |element: &Pattern<V>| {
507 let elem_id = element.value.identify();
508 let mut containers = Vec::new();
509
510 for rel in g7.pg_relationships.values() {
511 if rel.elements.len() == 2
512 && (rel.elements[0].value.identify() == elem_id
513 || rel.elements[1].value.identify() == elem_id)
514 {
515 containers.push(rel.clone());
516 }
517 }
518
519 for walk in g7.pg_walks.values() {
520 if walk.elements.iter().any(|e| e.value.identify() == elem_id) {
521 containers.push(walk.clone());
522 }
523 }
524
525 for ann in g7.pg_annotations.values() {
526 if ann.elements.len() == 1 && ann.elements[0].value.identify() == elem_id {
527 containers.push(ann.clone());
528 }
529 }
530
531 containers
532 });
533
534 GraphQuery {
535 query_nodes,
536 query_relationships,
537 query_incident_rels,
538 query_source,
539 query_target,
540 query_degree,
541 query_node_by_id,
542 query_relationship_by_id,
543 query_containers,
544 }
545}
546
547pub fn from_patterns_with_policy<Extra, V>(
549 classifier: &GraphClassifier<Extra, V>,
550 policy: &ReconciliationPolicy<V::MergeStrategy>,
551 patterns: impl IntoIterator<Item = Pattern<V>>,
552) -> PatternGraph<Extra, V>
553where
554 V: GraphValue<Id = Symbol>
555 + HasIdentity<V, Symbol>
556 + Mergeable
557 + Refinable
558 + PartialEq
559 + Clone
560 + 'static,
561 Extra: 'static,
562{
563 patterns.into_iter().fold(PatternGraph::empty(), |g, p| {
564 merge_with_policy(classifier, policy, p, g)
565 })
566}