use crate::{
LabelId, ProjectionId, PropertyKeyId, RelationTypeId, RoleId,
catalog::{
GraphProjectionDefinition, HypergraphProjectionDefinition, IndexDefinition,
ProjectionDefinition,
},
};
pub(crate) const DEF_PROJECTION_GRAPH: u32 = 0;
pub(crate) const DEF_PROJECTION_HYPER: u32 = 1;
pub(crate) const DEF_INDEX_LABEL: u32 = 0;
pub(crate) const DEF_INDEX_RELATION_TYPE: u32 = 1;
pub(crate) const DEF_INDEX_PROPERTY_EQUALITY: u32 = 2;
pub(crate) const DEF_INDEX_PROPERTY_RANGE: u32 = 3;
pub(crate) const DEF_INDEX_COMPOSITE_EQUALITY: u32 = 4;
pub(crate) const DEF_INDEX_PROJECTION: u32 = 5;
#[derive(Clone, Copy, Debug)]
pub(crate) struct DefDecodeError {
pub(crate) reason: &'static str,
}
const fn malformed(reason: &'static str) -> DefDecodeError {
DefDecodeError { reason }
}
fn push_id_set(words: &mut Vec<u64>, ids: impl ExactSizeIterator<Item = u64>) {
words.push(ids.len() as u64);
words.extend(ids);
}
fn read_id_set(words: &[u64], cursor: &mut usize) -> Result<Vec<u64>, DefDecodeError> {
let count = usize::try_from(
*words
.get(*cursor)
.ok_or(malformed("def missing id-set length"))?,
)
.map_err(|_overflow| malformed("def id-set length overflow"))?;
*cursor += 1;
let end = cursor
.checked_add(count)
.ok_or(malformed("def id-set overflow"))?;
let slice = words
.get(*cursor..end)
.ok_or(malformed("def id-set out of bounds"))?;
let ids = slice.to_vec();
*cursor = end;
Ok(ids)
}
pub(crate) fn encode_projection_body(definition: &ProjectionDefinition) -> (u32, Vec<u64>) {
let mut words = Vec::new();
match definition {
ProjectionDefinition::Graph(graph) => {
words.push(graph.source_role.get());
words.push(graph.target_role.get());
push_id_set(&mut words, graph.relation_types.iter().map(|id| id.get()));
(DEF_PROJECTION_GRAPH, words)
}
ProjectionDefinition::Hypergraph(hyper) => {
push_id_set(&mut words, hyper.source_roles.iter().map(|id| id.get()));
push_id_set(&mut words, hyper.target_roles.iter().map(|id| id.get()));
push_id_set(&mut words, hyper.relation_types.iter().map(|id| id.get()));
(DEF_PROJECTION_HYPER, words)
}
}
}
pub(crate) fn encode_index_body(definition: &IndexDefinition) -> (u32, Vec<u64>) {
match definition {
IndexDefinition::Label { label } => (DEF_INDEX_LABEL, vec![label.get()]),
IndexDefinition::RelationType { relation_type } => {
(DEF_INDEX_RELATION_TYPE, vec![relation_type.get()])
}
IndexDefinition::PropertyEquality { key } => (DEF_INDEX_PROPERTY_EQUALITY, vec![key.get()]),
IndexDefinition::PropertyRange { key } => (DEF_INDEX_PROPERTY_RANGE, vec![key.get()]),
IndexDefinition::CompositeEquality { keys } => (
DEF_INDEX_COMPOSITE_EQUALITY,
keys.iter().map(|key| key.get()).collect(),
),
IndexDefinition::Projection { projection } => {
(DEF_INDEX_PROJECTION, vec![projection.get()])
}
}
}
pub(crate) fn decode_projection_body(
discriminant: u32,
name: String,
words: &[u64],
) -> Result<ProjectionDefinition, DefDecodeError> {
match discriminant {
DEF_PROJECTION_GRAPH => {
let source_role = *words
.first()
.ok_or(malformed("graph def missing source role"))?;
let target_role = *words
.get(1)
.ok_or(malformed("graph def missing target role"))?;
let mut cursor = 2;
let relation_types = read_id_set(words, &mut cursor)?
.into_iter()
.map(RelationTypeId::new)
.collect();
Ok(ProjectionDefinition::Graph(GraphProjectionDefinition {
name,
relation_types,
source_role: RoleId::new(source_role),
target_role: RoleId::new(target_role),
}))
}
DEF_PROJECTION_HYPER => {
let mut cursor = 0;
let source_roles = read_id_set(words, &mut cursor)?
.into_iter()
.map(RoleId::new)
.collect();
let target_roles = read_id_set(words, &mut cursor)?
.into_iter()
.map(RoleId::new)
.collect();
let relation_types = read_id_set(words, &mut cursor)?
.into_iter()
.map(RelationTypeId::new)
.collect();
Ok(ProjectionDefinition::Hypergraph(
HypergraphProjectionDefinition {
name,
relation_types,
source_roles,
target_roles,
},
))
}
_other => Err(malformed("unknown projection definition kind")),
}
}
pub(crate) fn decode_index_body(
discriminant: u32,
words: &[u64],
) -> Result<IndexDefinition, DefDecodeError> {
let first = || {
words
.first()
.copied()
.ok_or(malformed("index def missing id"))
};
match discriminant {
DEF_INDEX_LABEL => Ok(IndexDefinition::Label {
label: LabelId::new(first()?),
}),
DEF_INDEX_RELATION_TYPE => Ok(IndexDefinition::RelationType {
relation_type: RelationTypeId::new(first()?),
}),
DEF_INDEX_PROPERTY_EQUALITY => Ok(IndexDefinition::PropertyEquality {
key: PropertyKeyId::new(first()?),
}),
DEF_INDEX_PROPERTY_RANGE => Ok(IndexDefinition::PropertyRange {
key: PropertyKeyId::new(first()?),
}),
DEF_INDEX_COMPOSITE_EQUALITY => Ok(IndexDefinition::CompositeEquality {
keys: words.iter().map(|word| PropertyKeyId::new(*word)).collect(),
}),
DEF_INDEX_PROJECTION => Ok(IndexDefinition::Projection {
projection: ProjectionId::new(first()?),
}),
_other => Err(malformed("unknown index definition kind")),
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::*;
fn projection_strategy() -> impl Strategy<Value = ProjectionDefinition> {
let ids = proptest::collection::btree_set(0u64..1_000, 0..8);
let graph = (any::<u64>(), any::<u64>(), ids.clone()).prop_map(
|(source, target, relation_types)| {
ProjectionDefinition::Graph(GraphProjectionDefinition {
name: "p".to_owned(),
relation_types: relation_types
.into_iter()
.map(RelationTypeId::new)
.collect(),
source_role: RoleId::new(source),
target_role: RoleId::new(target),
})
},
);
let hyper = (ids.clone(), ids.clone(), ids).prop_map(|(sources, targets, types)| {
ProjectionDefinition::Hypergraph(HypergraphProjectionDefinition {
name: "p".to_owned(),
relation_types: types.into_iter().map(RelationTypeId::new).collect(),
source_roles: sources.into_iter().map(RoleId::new).collect(),
target_roles: targets.into_iter().map(RoleId::new).collect(),
})
});
prop_oneof![graph, hyper]
}
fn index_strategy() -> impl Strategy<Value = IndexDefinition> {
prop_oneof![
any::<u64>().prop_map(|id| IndexDefinition::Label {
label: LabelId::new(id)
}),
any::<u64>().prop_map(|id| IndexDefinition::RelationType {
relation_type: RelationTypeId::new(id)
}),
any::<u64>().prop_map(|id| IndexDefinition::PropertyEquality {
key: PropertyKeyId::new(id)
}),
any::<u64>().prop_map(|id| IndexDefinition::PropertyRange {
key: PropertyKeyId::new(id)
}),
proptest::collection::vec(any::<u64>(), 1..6).prop_map(|keys| {
IndexDefinition::CompositeEquality {
keys: keys.into_iter().map(PropertyKeyId::new).collect(),
}
}),
any::<u64>().prop_map(|id| IndexDefinition::Projection {
projection: ProjectionId::new(id)
}),
]
}
proptest! {
#[test]
fn projection_body_roundtrips(definition in projection_strategy()) {
let (kind, words) = encode_projection_body(&definition);
let decoded = decode_projection_body(kind, "p".to_owned(), &words)
.expect("well-formed body decodes");
prop_assert_eq!(decoded, definition);
}
#[test]
fn index_body_roundtrips(definition in index_strategy()) {
let (kind, words) = encode_index_body(&definition);
let decoded = decode_index_body(kind, &words).expect("well-formed body decodes");
prop_assert_eq!(decoded, definition);
}
}
}