use crate::datatypes::values::Value;
use crate::graph::algorithms::Interrupt;
use crate::graph::constraints::{
ConstraintKind, ConstraintResult, ConstraintViolation, EntityKind,
};
use crate::graph::property_types::{self, DeclaredType};
use crate::graph::storage::interner::InternedKey;
use super::DirGraph;
const SCAN_POLL_INTERVAL: usize = 4096;
#[derive(Debug)]
pub(crate) enum RelDeclarationError {
Violated(Box<ConstraintViolation>),
Interrupted(String),
}
impl From<Box<ConstraintViolation>> for RelDeclarationError {
fn from(violation: Box<ConstraintViolation>) -> Self {
RelDeclarationError::Violated(violation)
}
}
pub(crate) type RelDeclarationResult<T> = Result<T, RelDeclarationError>;
impl DirGraph {
pub(crate) fn has_rel_not_null_constraint(&self, rel_type: &str, property: &str) -> bool {
self.rel_ddl_not_null_constraints
.contains(&(rel_type.to_string(), property.to_string()))
}
pub(crate) fn list_rel_not_null_constraints(&self) -> Vec<(String, String)> {
self.rel_ddl_not_null_constraints.iter().cloned().collect()
}
pub(crate) fn create_rel_not_null_constraint(
&mut self,
rel_type: &str,
property: &str,
interrupt: &Interrupt,
) -> RelDeclarationResult<usize> {
let (checked, missing) = self.count_rel_missing_property(rel_type, property, interrupt)?;
if missing > 0 {
return Err(RelDeclarationError::Violated(Box::new(
ConstraintViolation::preexisting_missing(
ConstraintKind::NotNull,
rel_type,
property,
missing,
)
.on_entity(EntityKind::Relationship),
)));
}
self.rel_ddl_not_null_constraints
.insert((rel_type.to_string(), property.to_string()));
Ok(checked)
}
pub(crate) fn drop_rel_not_null_constraint(&mut self, rel_type: &str, property: &str) -> bool {
self.rel_ddl_not_null_constraints
.remove(&(rel_type.to_string(), property.to_string()))
}
pub(crate) fn rel_property_type_for(
&self,
rel_type: &str,
property: &str,
) -> Option<DeclaredType> {
self.rel_ddl_property_type_constraints
.get(rel_type)?
.get(property)
.copied()
}
pub(crate) fn list_rel_property_type_constraints(&self) -> Vec<(String, String, DeclaredType)> {
self.rel_ddl_property_type_constraints
.iter()
.flat_map(|(rel_type, declared)| {
declared
.iter()
.map(move |(property, kind)| (rel_type.clone(), property.clone(), *kind))
})
.collect()
}
pub(crate) fn create_rel_property_type_constraint(
&mut self,
rel_type: &str,
property: &str,
declared: DeclaredType,
interrupt: &Interrupt,
) -> RelDeclarationResult<usize> {
let (checked, violations, sample) =
self.count_rel_type_violations(rel_type, property, declared, interrupt)?;
if violations > 0 {
return Err(RelDeclarationError::Violated(Box::new(
ConstraintViolation::preexisting_type_mismatch(
rel_type,
property,
declared.name(),
sample.unwrap_or("a value of another type"),
violations,
)
.on_entity(EntityKind::Relationship),
)));
}
self.rel_ddl_property_type_constraints
.entry(rel_type.to_string())
.or_default()
.insert(property.to_string(), declared);
Ok(checked)
}
pub(crate) fn drop_rel_property_type_constraint(
&mut self,
rel_type: &str,
property: &str,
) -> bool {
let Some(declared) = self.rel_ddl_property_type_constraints.get_mut(rel_type) else {
return false;
};
let removed = declared.remove(property).is_some();
if declared.is_empty() {
self.rel_ddl_property_type_constraints.remove(rel_type);
}
removed
}
#[inline]
pub(crate) fn has_rel_constraints(&self) -> bool {
!self.rel_ddl_not_null_constraints.is_empty()
|| !self.rel_ddl_property_type_constraints.is_empty()
}
#[inline]
pub(crate) fn type_has_rel_constraints(&self, rel_type: &str) -> bool {
self.type_has_rel_not_null_constraints(rel_type)
|| self.type_has_rel_property_type_constraints(rel_type)
}
#[inline]
pub(crate) fn type_has_rel_not_null_constraints(&self, rel_type: &str) -> bool {
self.rel_required_properties(rel_type).next().is_some()
}
#[inline]
pub(crate) fn type_has_rel_property_type_constraints(&self, rel_type: &str) -> bool {
self.rel_ddl_property_type_constraints
.contains_key(rel_type)
}
pub(crate) fn rel_required_properties<'a>(
&'a self,
rel_type: &'a str,
) -> impl Iterator<Item = &'a str> + 'a {
self.rel_ddl_not_null_constraints
.range((rel_type.to_string(), String::new())..)
.take_while(move |(declared, _)| declared == rel_type)
.map(|(_, property)| property.as_str())
}
#[inline]
pub(crate) fn rel_declared_property_types(
&self,
rel_type: &str,
) -> Option<&std::collections::BTreeMap<String, DeclaredType>> {
self.rel_ddl_property_type_constraints.get(rel_type)
}
pub(crate) fn rel_constrained_properties(&self, rel_type: &str) -> Vec<String> {
let mut names: Vec<String> = self
.rel_required_properties(rel_type)
.map(str::to_string)
.collect();
if let Some(declared) = self.rel_declared_property_types(rel_type) {
names.extend(declared.keys().cloned());
}
names.sort();
names.dedup();
names
}
pub(crate) fn check_rel_row<F>(&mut self, rel_type: &str, read: F) -> Result<(), String>
where
F: Fn(&str) -> Option<Value>,
{
match self.check_rel_row_uncaught(rel_type, read) {
Ok(()) => Ok(()),
Err(violation) => Err(self.record_constraint_violation(*violation)),
}
}
fn check_rel_row_uncaught<F>(&self, rel_type: &str, read: F) -> ConstraintResult<()>
where
F: Fn(&str) -> Option<Value>,
{
for property in self.rel_required_properties(rel_type) {
match read(property) {
Some(Value::Null) | None => {
return Err(Box::new(
ConstraintViolation::missing(ConstraintKind::NotNull, rel_type, property)
.on_entity(EntityKind::Relationship),
))
}
Some(_) => {}
}
}
let Some(declared) = self.rel_declared_property_types(rel_type) else {
return Ok(());
};
for (property, expected) in declared {
let Some(value) = read(property) else {
continue;
};
Self::rel_type_violation(*expected, rel_type, property, &value)?;
}
Ok(())
}
pub(crate) fn check_rel_property_write(
&mut self,
rel_type: &str,
property: &str,
new_value: Option<&Value>,
) -> Result<(), String> {
match self.check_rel_property_write_uncaught(rel_type, property, new_value) {
Ok(()) => Ok(()),
Err(violation) => Err(self.record_constraint_violation(*violation)),
}
}
fn check_rel_property_write_uncaught(
&self,
rel_type: &str,
property: &str,
new_value: Option<&Value>,
) -> ConstraintResult<()> {
match new_value {
Some(Value::Null) | None => {
if self.has_rel_not_null_constraint(rel_type, property) {
return Err(Box::new(
ConstraintViolation::missing(ConstraintKind::NotNull, rel_type, property)
.on_entity(EntityKind::Relationship),
));
}
Ok(())
}
Some(value) => match self.rel_property_type_for(rel_type, property) {
Some(declared) => Self::rel_type_violation(declared, rel_type, property, value),
None => Ok(()),
},
}
}
fn rel_type_violation(
declared: DeclaredType,
rel_type: &str,
property: &str,
value: &Value,
) -> ConstraintResult<()> {
if declared.accepts(value) {
return Ok(());
}
Err(Box::new(
ConstraintViolation::type_mismatch(
rel_type,
property,
declared.name(),
property_types::value_type_name(value),
)
.on_entity(EntityKind::Relationship),
))
}
fn for_each_rel_of_type<F>(
&self,
rel_type: &str,
interrupt: &Interrupt,
mut visit: F,
) -> Result<usize, String>
where
F: FnMut(&[(InternedKey, Value)]),
{
if self.has_edge_type_counts_cache()
&& self
.get_edge_type_counts()
.get(rel_type)
.is_none_or(|count| *count == 0)
{
return Ok(0);
}
let conn_key = InternedKey::from_str(rel_type);
let mut visited = 0usize;
let mut interrupted = false;
self.graph.for_each_edge_of_conn_type(
conn_key,
|_source, _target, _edge_idx, properties| {
if visited & (SCAN_POLL_INTERVAL - 1) == 0 && interrupt.exceeded() {
interrupted = true;
return false;
}
visited += 1;
visit(properties);
true
},
);
if interrupted {
return Err(format!(
"declaring a constraint on relationship type '{rel_type}' was interrupted after \
{visited} relationships: the declaration is verified against the existing data, \
which is a scan of every relationship of the type. Nothing was installed. Raise \
the timeout, or declare the constraint before loading the data."
));
}
Ok(visited)
}
#[inline]
fn rel_property(properties: &[(InternedKey, Value)], key: InternedKey) -> Option<&Value> {
properties
.iter()
.find(|(stored, _)| *stored == key)
.map(|(_, value)| value)
}
fn count_rel_missing_property(
&self,
rel_type: &str,
property: &str,
interrupt: &Interrupt,
) -> RelDeclarationResult<(usize, usize)> {
let key = InternedKey::from_str(property);
let mut missing = 0usize;
let checked = self
.for_each_rel_of_type(rel_type, interrupt, |properties| {
match Self::rel_property(properties, key) {
Some(Value::Null) | None => missing += 1,
Some(_) => {}
}
})
.map_err(RelDeclarationError::Interrupted)?;
Ok((checked, missing))
}
fn count_rel_type_violations(
&self,
rel_type: &str,
property: &str,
declared: DeclaredType,
interrupt: &Interrupt,
) -> RelDeclarationResult<(usize, usize, Option<&'static str>)> {
let key = InternedKey::from_str(property);
let mut violations = 0usize;
let mut sample: Option<&'static str> = None;
let checked = self
.for_each_rel_of_type(rel_type, interrupt, |properties| {
let Some(value) = Self::rel_property(properties, key) else {
return;
};
if matches!(value, Value::Null) || declared.accepts(value) {
return;
}
violations += 1;
sample.get_or_insert_with(|| property_types::value_type_name(value));
})
.map_err(RelDeclarationError::Interrupted)?;
Ok((checked, violations, sample))
}
}