use std::collections::{HashMap, HashSet};
use panproto_inst::CompiledMigration;
use panproto_schema::Schema;
use crate::error::ExistenceError;
use crate::migration::Migration;
pub fn compile(
src: &Schema,
tgt: &Schema,
migration: &Migration,
) -> Result<CompiledMigration, ExistenceError> {
let mut surviving_verts = HashSet::new();
let mut vertex_remap = HashMap::new();
for (src_v, tgt_v) in &migration.vertex_map {
if !tgt.has_vertex(tgt_v) {
return Err(ExistenceError::WellFormedness {
message: format!("vertex_map target {tgt_v} (from {src_v}) not in target schema"),
});
}
surviving_verts.insert(tgt_v.clone());
vertex_remap.insert(src_v.clone(), tgt_v.clone());
}
let mut surviving_edges = HashSet::new();
let mut edge_remap = HashMap::new();
for (src_e, tgt_e) in &migration.edge_map {
surviving_edges.insert(tgt_e.clone());
edge_remap.insert(src_e.clone(), tgt_e.clone());
}
for edge in tgt.edges.keys() {
if surviving_verts.contains(&edge.src) && surviving_verts.contains(&edge.tgt) {
surviving_edges.insert(edge.clone());
}
}
let mut field_transforms = HashMap::new();
for (src_v, tgt_v) in &migration.vertex_map {
if let (Some(src_vert), Some(tgt_vert)) = (src.vertex(src_v), tgt.vertex(tgt_v)) {
if src_vert.kind != tgt_vert.kind {
if let Some(coercion_spec) = tgt
.coercions
.get(&(src_vert.kind.clone(), tgt_vert.kind.clone()))
{
field_transforms
.entry(src_v.clone())
.or_insert_with(Vec::new)
.push(panproto_inst::FieldTransform::ApplyExpr {
key: "__value__".to_string(),
expr: coercion_spec.forward.clone(),
inverse: coercion_spec.inverse.clone(),
coercion_class: coercion_spec.class,
});
}
}
}
}
let resolver = migration.resolver.clone();
let mut hyper_resolver = HashMap::new();
for ((he_id, _labels), (tgt_he_id, label_map)) in &migration.hyper_resolver {
hyper_resolver.insert(he_id.clone(), (tgt_he_id.clone(), label_map.clone()));
}
Ok(CompiledMigration {
surviving_verts,
surviving_edges,
vertex_remap,
edge_remap,
resolver,
hyper_resolver,
field_transforms,
conditional_survival: HashMap::new(),
expansion_path: HashMap::new(),
})
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use panproto_gat::Name;
use panproto_schema::{Edge, Vertex};
fn test_schema(vertices: &[(&str, &str)], edges: &[Edge]) -> Schema {
let mut vert_map = HashMap::new();
let mut edge_map = HashMap::new();
let mut outgoing: HashMap<Name, smallvec::SmallVec<Edge, 4>> = HashMap::new();
let mut incoming: HashMap<Name, smallvec::SmallVec<Edge, 4>> = HashMap::new();
let mut between: HashMap<(Name, Name), smallvec::SmallVec<Edge, 2>> = HashMap::new();
for (id, kind) in vertices {
vert_map.insert(
Name::from(*id),
Vertex {
id: Name::from(*id),
kind: Name::from(*kind),
nsid: None,
},
);
}
for edge in edges {
edge_map.insert(edge.clone(), edge.kind.clone());
outgoing
.entry(edge.src.clone())
.or_default()
.push(edge.clone());
incoming
.entry(edge.tgt.clone())
.or_default()
.push(edge.clone());
between
.entry((edge.src.clone(), edge.tgt.clone()))
.or_default()
.push(edge.clone());
}
Schema {
protocol: "test".into(),
vertices: vert_map,
edges: edge_map,
hyper_edges: HashMap::new(),
constraints: HashMap::new(),
required: HashMap::new(),
nsids: HashMap::new(),
entries: Vec::new(),
variants: HashMap::new(),
orderings: HashMap::new(),
recursion_points: HashMap::new(),
spans: HashMap::new(),
usage_modes: HashMap::new(),
nominal: HashMap::new(),
coercions: HashMap::new(),
mergers: HashMap::new(),
defaults: HashMap::new(),
policies: HashMap::new(),
outgoing,
incoming,
between,
}
}
#[test]
fn compile_identity() {
let edge = Edge {
src: "a".into(),
tgt: "b".into(),
kind: "prop".into(),
name: Some("x".into()),
};
let schema = test_schema(
&[("a", "object"), ("b", "string")],
std::slice::from_ref(&edge),
);
let mig = Migration::identity(&["a".into(), "b".into()], std::slice::from_ref(&edge));
let compiled = compile(&schema, &schema, &mig);
assert!(compiled.is_ok());
let c = compiled.unwrap_or_else(|_| panic!("compile should succeed"));
assert_eq!(c.surviving_verts.len(), 2);
assert!(c.surviving_verts.contains("a"));
assert!(c.surviving_verts.contains("b"));
assert!(c.surviving_edges.contains(&edge));
}
#[test]
#[allow(clippy::similar_names)]
fn compile_projection_drops_vertex() {
let edge_ab = Edge {
src: "a".into(),
tgt: "b".into(),
kind: "prop".into(),
name: Some("x".into()),
};
let edge_ac = Edge {
src: "a".into(),
tgt: "c".into(),
kind: "prop".into(),
name: Some("y".into()),
};
let src = test_schema(
&[("a", "object"), ("b", "string"), ("c", "string")],
&[edge_ab.clone(), edge_ac],
);
let tgt = test_schema(
&[("a", "object"), ("b", "string")],
std::slice::from_ref(&edge_ab),
);
let mig = Migration {
vertex_map: HashMap::from([("a".into(), "a".into()), ("b".into(), "b".into())]),
edge_map: HashMap::from([(edge_ab.clone(), edge_ab)]),
hyper_edge_map: HashMap::new(),
label_map: HashMap::new(),
resolver: HashMap::new(),
hyper_resolver: HashMap::new(),
expr_resolvers: HashMap::new(),
};
let compiled = compile(&src, &tgt, &mig);
assert!(compiled.is_ok());
let c = compiled.unwrap_or_else(|_| panic!("compile should succeed"));
assert_eq!(c.surviving_verts.len(), 2);
assert!(!c.surviving_verts.contains("c"));
}
}