1use panproto_schema::Schema;
9
10use crate::error::RestrictError;
11use crate::functor::FInstance;
12use crate::ginstance::GInstance;
13use crate::wtype::{CompiledMigration, WInstance};
14
15pub trait AcsetOps: Clone + std::fmt::Debug {
17 fn restrict(
25 &self,
26 src_schema: &Schema,
27 tgt_schema: &Schema,
28 migration: &CompiledMigration,
29 ) -> Result<Self, RestrictError>;
30
31 fn extend(
37 &self,
38 tgt_schema: &Schema,
39 migration: &CompiledMigration,
40 ) -> Result<Self, RestrictError>;
41
42 fn element_count(&self) -> usize;
44
45 fn shape_name(&self) -> &'static str;
47}
48
49impl AcsetOps for WInstance {
50 fn restrict(
51 &self,
52 src_schema: &Schema,
53 tgt_schema: &Schema,
54 migration: &CompiledMigration,
55 ) -> Result<Self, RestrictError> {
56 crate::wtype::wtype_restrict(self, src_schema, tgt_schema, migration)
57 }
58
59 fn extend(
60 &self,
61 tgt_schema: &Schema,
62 migration: &CompiledMigration,
63 ) -> Result<Self, RestrictError> {
64 crate::wtype::wtype_extend(self, tgt_schema, migration)
65 }
66
67 fn element_count(&self) -> usize {
68 self.node_count()
69 }
70
71 fn shape_name(&self) -> &'static str {
72 "wtype"
73 }
74}
75
76impl AcsetOps for FInstance {
77 fn restrict(
78 &self,
79 _src_schema: &Schema,
80 _tgt_schema: &Schema,
81 migration: &CompiledMigration,
82 ) -> Result<Self, RestrictError> {
83 crate::functor::functor_restrict(self, migration)
84 }
85
86 fn extend(
87 &self,
88 _tgt_schema: &Schema,
89 migration: &CompiledMigration,
90 ) -> Result<Self, RestrictError> {
91 crate::functor::functor_extend(self, migration)
92 }
93
94 fn element_count(&self) -> usize {
95 self.table_count()
96 }
97
98 fn shape_name(&self) -> &'static str {
99 "functor"
100 }
101}
102
103impl AcsetOps for GInstance {
104 fn restrict(
105 &self,
106 _src_schema: &Schema,
107 _tgt_schema: &Schema,
108 migration: &CompiledMigration,
109 ) -> Result<Self, RestrictError> {
110 crate::ginstance::graph_restrict(self, migration)
111 }
112
113 fn extend(
114 &self,
115 _tgt_schema: &Schema,
116 migration: &CompiledMigration,
117 ) -> Result<Self, RestrictError> {
118 crate::ginstance::graph_extend(self, migration)
119 }
120
121 fn element_count(&self) -> usize {
122 self.node_count()
123 }
124
125 fn shape_name(&self) -> &'static str {
126 "graph"
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use std::collections::{HashMap, HashSet};
133
134 use panproto_gat::Name;
135 use panproto_schema::Edge;
136
137 use super::*;
138 use crate::metadata::Node;
139 use crate::value::{FieldPresence, Value};
140
141 fn make_empty_schema() -> Schema {
146 Schema {
147 protocol: "test".into(),
148 vertices: HashMap::new(),
149 edges: HashMap::new(),
150 hyper_edges: HashMap::new(),
151 constraints: HashMap::new(),
152 required: HashMap::new(),
153 nsids: HashMap::new(),
154 entries: Vec::new(),
155 variants: HashMap::new(),
156 orderings: HashMap::new(),
157 recursion_points: HashMap::new(),
158 spans: HashMap::new(),
159 usage_modes: HashMap::new(),
160 nominal: HashMap::new(),
161 coercions: HashMap::new(),
162 mergers: HashMap::new(),
163 defaults: HashMap::new(),
164 policies: HashMap::new(),
165 outgoing: HashMap::new(),
166 incoming: HashMap::new(),
167 between: HashMap::new(),
168 }
169 }
170
171 fn make_test_schema(vertices: &[&str], edges: &[Edge]) -> Schema {
172 use smallvec::smallvec;
173 let mut between = HashMap::new();
174 for edge in edges {
175 between
176 .entry((Name::from(&*edge.src), Name::from(&*edge.tgt)))
177 .or_insert_with(|| smallvec![])
178 .push(edge.clone());
179 }
180 Schema {
181 protocol: "test".into(),
182 vertices: vertices
183 .iter()
184 .map(|&v| {
185 (
186 Name::from(v),
187 panproto_schema::Vertex {
188 id: Name::from(v),
189 kind: Name::from("object"),
190 nsid: None,
191 },
192 )
193 })
194 .collect(),
195 edges: HashMap::new(),
196 hyper_edges: HashMap::new(),
197 constraints: HashMap::new(),
198 required: HashMap::new(),
199 nsids: HashMap::new(),
200 entries: Vec::new(),
201 variants: HashMap::new(),
202 orderings: HashMap::new(),
203 recursion_points: HashMap::new(),
204 spans: HashMap::new(),
205 usage_modes: HashMap::new(),
206 nominal: HashMap::new(),
207 coercions: HashMap::new(),
208 mergers: HashMap::new(),
209 defaults: HashMap::new(),
210 policies: HashMap::new(),
211 outgoing: HashMap::new(),
212 incoming: HashMap::new(),
213 between,
214 }
215 }
216
217 fn identity_migration(verts: &[&str], edges: &[Edge]) -> CompiledMigration {
218 CompiledMigration {
219 surviving_verts: verts.iter().map(|&v| Name::from(v)).collect(),
220 surviving_edges: edges.iter().cloned().collect(),
221 vertex_remap: HashMap::new(),
222 edge_remap: HashMap::new(),
223 resolver: HashMap::new(),
224 hyper_resolver: HashMap::new(),
225 field_transforms: HashMap::new(),
226 conditional_survival: HashMap::new(),
227 expansion_path: HashMap::new(),
228 }
229 }
230
231 fn three_node_winstance() -> WInstance {
232 let mut nodes = HashMap::new();
233 nodes.insert(0, Node::new(0, "post:body"));
234 nodes.insert(
235 1,
236 Node::new(1, "post:body.text")
237 .with_value(FieldPresence::Present(Value::Str("hello".into()))),
238 );
239 nodes.insert(
240 2,
241 Node::new(2, "post:body.createdAt")
242 .with_value(FieldPresence::Present(Value::Str("2024-01-01".into()))),
243 );
244 let arcs = vec![
245 (
246 0,
247 1,
248 Edge {
249 src: "post:body".into(),
250 tgt: "post:body.text".into(),
251 kind: "prop".into(),
252 name: Some("text".into()),
253 },
254 ),
255 (
256 0,
257 2,
258 Edge {
259 src: "post:body".into(),
260 tgt: "post:body.createdAt".into(),
261 kind: "prop".into(),
262 name: Some("createdAt".into()),
263 },
264 ),
265 ];
266 WInstance::new(nodes, arcs, vec![], 0, Name::from("post:body"))
267 }
268
269 fn two_node_finstance() -> FInstance {
270 let mut row = HashMap::new();
271 row.insert("name".to_string(), Value::Str("Alice".into()));
272 FInstance::new().with_table("users", vec![row])
273 }
274
275 fn two_node_ginstance() -> (GInstance, Edge) {
276 let edge = Edge {
277 src: "person".into(),
278 tgt: "person".into(),
279 kind: "knows".into(),
280 name: None,
281 };
282 let g = GInstance::new()
283 .with_node(Node::new(0, "person"))
284 .with_node(Node::new(1, "person"))
285 .with_edge(0, 1, edge.clone())
286 .with_value(0, Value::Str("Alice".into()))
287 .with_value(1, Value::Str("Bob".into()));
288 (g, edge)
289 }
290
291 #[test]
296 fn winstance_shape_name() {
297 let w = three_node_winstance();
298 assert_eq!(AcsetOps::shape_name(&w), "wtype");
299 }
300
301 #[test]
302 fn finstance_shape_name() {
303 let f = two_node_finstance();
304 assert_eq!(AcsetOps::shape_name(&f), "functor");
305 }
306
307 #[test]
308 fn ginstance_shape_name() {
309 let (g, _) = two_node_ginstance();
310 assert_eq!(AcsetOps::shape_name(&g), "graph");
311 }
312
313 #[test]
318 fn winstance_element_count() {
319 let w = three_node_winstance();
320 assert_eq!(AcsetOps::element_count(&w), 3);
321 }
322
323 #[test]
324 fn finstance_element_count() {
325 let f = two_node_finstance();
326 assert_eq!(AcsetOps::element_count(&f), 1);
327 }
328
329 #[test]
330 fn ginstance_element_count() {
331 let (g, _) = two_node_ginstance();
332 assert_eq!(AcsetOps::element_count(&g), 2);
333 }
334
335 #[test]
340 fn winstance_restrict_via_trait() -> Result<(), Box<dyn std::error::Error>> {
341 let w = three_node_winstance();
342 let edge_text = Edge {
343 src: "post:body".into(),
344 tgt: "post:body.text".into(),
345 kind: "prop".into(),
346 name: Some("text".into()),
347 };
348 let tgt_schema = make_test_schema(&["post:body", "post:body.text"], &[edge_text]);
349 let src_schema = make_empty_schema();
350 let migration = CompiledMigration {
351 surviving_verts: HashSet::from([Name::from("post:body"), Name::from("post:body.text")]),
352 surviving_edges: HashSet::new(),
353 vertex_remap: HashMap::new(),
354 edge_remap: HashMap::new(),
355 resolver: HashMap::new(),
356 hyper_resolver: HashMap::new(),
357 field_transforms: HashMap::new(),
358 conditional_survival: HashMap::new(),
359 expansion_path: HashMap::new(),
360 };
361
362 let via_trait = AcsetOps::restrict(&w, &src_schema, &tgt_schema, &migration)?;
363 let via_fn = crate::wtype::wtype_restrict(&w, &src_schema, &tgt_schema, &migration)?;
364 assert_eq!(via_trait.node_count(), via_fn.node_count());
365 assert_eq!(via_trait.arc_count(), via_fn.arc_count());
366 Ok(())
367 }
368
369 #[test]
370 fn finstance_restrict_via_trait() -> Result<(), Box<dyn std::error::Error>> {
371 let f = two_node_finstance();
372 let schema = make_empty_schema();
373 let migration = identity_migration(&["users"], &[]);
374
375 let via_trait = AcsetOps::restrict(&f, &schema, &schema, &migration)?;
376 let via_fn = crate::functor::functor_restrict(&f, &migration)?;
377 assert_eq!(via_trait.table_count(), via_fn.table_count());
378 Ok(())
379 }
380
381 #[test]
382 fn ginstance_restrict_via_trait() -> Result<(), Box<dyn std::error::Error>> {
383 let (g, _edge) = two_node_ginstance();
384 let schema = make_empty_schema();
385 let migration = CompiledMigration {
386 surviving_verts: HashSet::from([Name::from("person_new")]),
387 surviving_edges: HashSet::new(),
388 vertex_remap: HashMap::from([("person".into(), "person_new".into())]),
389 edge_remap: HashMap::new(),
390 resolver: HashMap::new(),
391 hyper_resolver: HashMap::new(),
392 field_transforms: HashMap::new(),
393 conditional_survival: HashMap::new(),
394 expansion_path: HashMap::new(),
395 };
396
397 let via_trait = AcsetOps::restrict(&g, &schema, &schema, &migration)?;
398 let via_fn = crate::ginstance::graph_restrict(&g, &migration)?;
399 assert_eq!(via_trait.node_count(), via_fn.node_count());
400 assert_eq!(via_trait.edge_count(), via_fn.edge_count());
401 Ok(())
402 }
403
404 #[test]
409 fn graph_extend_identity_preserves_instance() -> Result<(), Box<dyn std::error::Error>> {
410 let (g, edge) = two_node_ginstance();
411 let schema = make_empty_schema();
412 let migration = identity_migration(&["person"], &[edge]);
413
414 let result = AcsetOps::extend(&g, &schema, &migration)?;
415 assert_eq!(result.node_count(), 2);
416 assert_eq!(result.edge_count(), 1);
417 assert_eq!(result.values.len(), 2);
418 assert_eq!(result.values[&0], Value::Str("Alice".into()));
419 assert_eq!(result.values[&1], Value::Str("Bob".into()));
420 Ok(())
421 }
422
423 #[test]
424 fn graph_extend_vertex_remap_updates_anchors() -> Result<(), Box<dyn std::error::Error>> {
425 let (g, edge) = two_node_ginstance();
426 let schema = make_empty_schema();
427 let new_edge = Edge {
428 src: "human".into(),
429 tgt: "human".into(),
430 kind: "knows".into(),
431 name: None,
432 };
433 let migration = CompiledMigration {
434 surviving_verts: HashSet::from([Name::from("human")]),
435 surviving_edges: HashSet::new(),
436 vertex_remap: HashMap::from([("person".into(), "human".into())]),
437 edge_remap: HashMap::from([(edge, new_edge.clone())]),
438 resolver: HashMap::new(),
439 hyper_resolver: HashMap::new(),
440 field_transforms: HashMap::new(),
441 conditional_survival: HashMap::new(),
442 expansion_path: HashMap::new(),
443 };
444
445 let result = AcsetOps::extend(&g, &schema, &migration)?;
446 assert_eq!(result.node_count(), 2);
447 assert_eq!(result.nodes[&0].anchor, Name::from("human"));
448 assert_eq!(result.nodes[&1].anchor, Name::from("human"));
449 assert_eq!(result.edge_count(), 1);
450 assert_eq!(result.edges[0].2, new_edge);
451 assert_eq!(result.values[&0], Value::Str("Alice".into()));
453 Ok(())
454 }
455}