diff --git a/src/algo/mod.rs b/src/algo/mod.rs index 4141be06..c0393d1f 100644 --- a/src/algo/mod.rs +++ b/src/algo/mod.rs @@ -2,8 +2,9 @@ use std::collections::BTreeMap; use either::Either; use itertools::Itertools; -use miette::{bail, ensure, miette, Result}; +use miette::{bail, ensure, Diagnostic, Result}; use smartstring::{LazyCompact, SmartString}; +use thiserror::Error; use crate::algo::all_pairs_shortest_path::{BetweennessCentrality, ClosenessCentrality}; use crate::algo::astar::ShortestPathAStar; @@ -136,11 +137,45 @@ impl AlgoHandle { "label_propagation" => Box::new(LabelPropagation), "random_walk" => Box::new(RandomWalk), "reorder_sort" => Box::new(ReorderSort), - name => bail!("algorithm '{}' not found", name), + name => bail!(AlgoNotFoundError(name.to_string(), self.name.span)), }) } } +#[derive(Error, Diagnostic, Debug)] +#[error("The relation cannot be interpreted as an edge")] +#[diagnostic(code(algo::not_an_edge))] +#[diagnostic(help("Edge relation requires tuples of length at least two"))] +struct NotAnEdgeError(#[label] SourceSpan); + +#[derive(Error, Diagnostic, Debug)] +#[error( + "The value {0:?} at the third position in the relation cannot be interpreted as edge weights" +)] +#[diagnostic(code(algo::invalid_edge_weight))] +#[diagnostic(help( + "Edge weights must be finite numbers. Some algorithm also requires positivity." +))] +struct BadEdgeWeightError(DataValue, #[label] SourceSpan); + +#[derive(Error, Diagnostic, Debug)] +#[error("The requested rule '{0}' cannot be found")] +#[diagnostic(code(algo::rule_not_found))] +struct RuleNotFoundError(String, #[label] SourceSpan); + +#[derive(Error, Diagnostic, Debug)] +#[error("Invalid reverse scanning of triples")] +#[diagnostic(code(algo::invalid_reverse_triple_scan))] +#[diagnostic(help( + "Inverse scanning of triples requires the type to be 'ref', or the value be indexed" +))] +struct InvalidInverseTripleUse(String, #[label] SourceSpan); + +#[derive(Error, Diagnostic, Debug)] +#[error("The requested algorithm '{0}' is not found")] +#[diagnostic(code(parser::algo_not_found))] +pub(crate) struct AlgoNotFoundError(pub(crate) String, #[label] pub(crate) SourceSpan); + impl MagicAlgoRuleArg { pub(crate) fn convert_edge_to_weighted_graph( &self, @@ -161,26 +196,45 @@ impl MagicAlgoRuleArg { for tuple in self.iter(tx, stores)? { let mut tuple = tuple?.0.into_iter(); - let from = tuple - .next() - .ok_or_else(|| miette!("edges relation too short"))?; - let to = tuple - .next() - .ok_or_else(|| miette!("edges relation too short"))?; + let from = tuple.next().ok_or_else(|| NotAnEdgeError(self.span()))?; + let to = tuple.next().ok_or_else(|| NotAnEdgeError(self.span()))?; let weight = match tuple.next() { None => 1.0, Some(d) => match d.get_float() { Some(f) => { - ensure!(f.is_finite(), "edge weight must be finite, got {}", f); + ensure!( + f.is_finite(), + BadEdgeWeightError( + d, + self.bindings() + .get(2) + .map(|s| s.span) + .unwrap_or(self.span()) + ) + ); if f < 0. { if !allow_negative_edges { - bail!("edge weight must be non-negative, got {}", f); + bail!(BadEdgeWeightError( + d, + self.bindings() + .get(2) + .map(|s| s.span) + .unwrap_or(self.span()) + )); } has_neg_edge = true; } f } - None => bail!("edge weight must be a number, got {:?}", d), + None => { + bail!(BadEdgeWeightError( + d, + self.bindings() + .get(2) + .map(|s| s.span) + .unwrap_or(self.span()) + )) + } }, }; let from_idx = if let Some(idx) = inv_indices.get(&from) { @@ -220,12 +274,8 @@ impl MagicAlgoRuleArg { for tuple in self.iter(tx, stores)? { let mut tuple = tuple?.0.into_iter(); - let from = tuple - .next() - .ok_or_else(|| miette!("edges relation too short"))?; - let to = tuple - .next() - .ok_or_else(|| miette!("edges relation too short"))?; + let from = tuple.next().ok_or_else(|| NotAnEdgeError(self.span()))?; + let to = tuple.next().ok_or_else(|| NotAnEdgeError(self.span()))?; let from_idx = if let Some(idx) = inv_indices.get(&from) { *idx } else { @@ -260,9 +310,9 @@ impl MagicAlgoRuleArg { ) -> Result> { Ok(match self { MagicAlgoRuleArg::InMem { name, .. } => { - let store = stores - .get(name) - .ok_or_else(|| miette!("rule not found: {:?}", name))?; + let store = stores.get(name).ok_or_else(|| { + RuleNotFoundError(name.symbol().to_string(), name.symbol().span) + })?; let t = Tuple(vec![prefix.clone()]); Box::new(store.scan_prefix(&t)) } @@ -271,53 +321,63 @@ impl MagicAlgoRuleArg { let t = Tuple(vec![prefix.clone()]); Box::new(relation.scan_prefix(tx, &t)) } - MagicAlgoRuleArg::Triple { name, dir, vld, .. } => { - if *dir == TripleDir::Bwd && !name.val_type.is_ref_type() { + MagicAlgoRuleArg::Triple { + attr, + dir, + vld, + span, + .. + } => { + if *dir == TripleDir::Bwd && !attr.val_type.is_ref_type() { ensure!( - name.indexing.should_index(), - "reverse scanning of triple values requires indexing: {:?}", - name.name + attr.indexing.should_index(), + InvalidInverseTripleUse(attr.name.to_string(), *span) ); - if name.with_history { + if attr.with_history { Box::new( - tx.triple_av_before_scan(name.id, prefix, *vld) + tx.triple_av_before_scan(attr.id, prefix, *vld) .map_ok(|(_, v, eid)| Tuple(vec![v, eid.as_datavalue()])), ) } else { Box::new( - tx.triple_av_scan(name.id, prefix) + tx.triple_av_scan(attr.id, prefix) .map_ok(|(_, v, eid)| Tuple(vec![v, eid.as_datavalue()])), ) } } else { - let id = prefix.get_int().ok_or_else(|| { - miette!( - "prefix scanning of triple requires integer id, got {:?}", - prefix - ) - })?; + #[derive(Error, Diagnostic, Debug)] + #[error("Encountered bad prefix value {0:?} during triple prefix scanning")] + #[diagnostic(code(algo::invalid_triple_prefix))] + #[diagnostic(help( + "Triple prefix should be an entity ID represented by an integer" + ))] + struct InvalidTriplePrefixError(DataValue, #[label] SourceSpan); + + let id = prefix + .get_int() + .ok_or_else(|| InvalidTriplePrefixError(prefix.clone(), self.span()))?; let id = EntityId(id as u64); match dir { TripleDir::Fwd => { - if name.with_history { + if attr.with_history { Box::new( - tx.triple_ae_before_scan(name.id, id, *vld) + tx.triple_ae_before_scan(attr.id, id, *vld) .map_ok(|(_, eid, v)| Tuple(vec![eid.as_datavalue(), v])), ) } else { Box::new( - tx.triple_ae_scan(name.id, id) + tx.triple_ae_scan(attr.id, id) .map_ok(|(_, eid, v)| Tuple(vec![eid.as_datavalue(), v])), ) } } TripleDir::Bwd => { - if name.with_history { - Box::new(tx.triple_vref_a_before_scan(id, name.id, *vld).map_ok( + if attr.with_history { + Box::new(tx.triple_vref_a_before_scan(id, attr.id, *vld).map_ok( |(v, _, eid)| Tuple(vec![v.as_datavalue(), eid.as_datavalue()]), )) } else { - Box::new(tx.triple_vref_a_scan(id, name.id).map_ok( + Box::new(tx.triple_vref_a_scan(id, attr.id).map_ok( |(v, _, eid)| Tuple(vec![v.as_datavalue(), eid.as_datavalue()]), )) } @@ -334,9 +394,9 @@ impl MagicAlgoRuleArg { ) -> Result { Ok(match self { MagicAlgoRuleArg::InMem { name, .. } => { - let store = stores - .get(name) - .ok_or_else(|| miette!("rule not found: {:?}", name))?; + let store = stores.get(name).ok_or_else(|| { + RuleNotFoundError(name.symbol().to_string(), name.symbol().span) + })?; store.arity } MagicAlgoRuleArg::Stored { name, .. } => { @@ -353,16 +413,21 @@ impl MagicAlgoRuleArg { ) -> Result> { Ok(match self { MagicAlgoRuleArg::InMem { name, .. } => { - let store = stores - .get(name) - .ok_or_else(|| miette!("rule not found: {:?}", name))?; + let store = stores.get(name).ok_or_else(|| { + RuleNotFoundError(name.symbol().to_string(), name.symbol().span) + })?; Box::new(store.scan_all()) } MagicAlgoRuleArg::Stored { name, .. } => { let relation = tx.get_relation(name)?; Box::new(relation.scan_all(tx)?) } - MagicAlgoRuleArg::Triple { name, dir, vld, .. } => match dir { + MagicAlgoRuleArg::Triple { + attr: name, + dir, + vld, + .. + } => match dir { TripleDir::Fwd => { if name.with_history { Box::new( diff --git a/src/data/program.rs b/src/data/program.rs index 07f9fb1d..baf72e93 100644 --- a/src/data/program.rs +++ b/src/data/program.rs @@ -224,15 +224,18 @@ pub(crate) enum AlgoRuleArg { InMem { name: Symbol, bindings: Vec, + span: SourceSpan, }, Stored { name: Symbol, bindings: Vec, + span: SourceSpan, }, Triple { name: Symbol, bindings: Vec, dir: TripleDir, + span: SourceSpan, }, } @@ -241,20 +244,37 @@ pub(crate) enum MagicAlgoRuleArg { InMem { name: MagicSymbol, bindings: Vec, + span: SourceSpan, }, Stored { name: Symbol, bindings: Vec, + span: SourceSpan, }, Triple { - name: Attribute, + attr: Attribute, bindings: Vec, dir: TripleDir, vld: Validity, + span: SourceSpan, }, } impl MagicAlgoRuleArg { + pub(crate) fn bindings(&self) -> &[Symbol] { + match self { + MagicAlgoRuleArg::InMem { bindings, .. } + | MagicAlgoRuleArg::Stored { bindings, .. } + | MagicAlgoRuleArg::Triple { bindings, .. } => bindings, + } + } + pub(crate) fn span(&self) -> SourceSpan { + match self { + MagicAlgoRuleArg::InMem { span, .. } + | MagicAlgoRuleArg::Stored { span, .. } + | MagicAlgoRuleArg::Triple { span, .. } => *span, + } + } pub(crate) fn get_binding_map(&self, starting: usize) -> BTreeMap { let bindings = match self { MagicAlgoRuleArg::InMem { bindings, .. } diff --git a/src/parse/query.rs b/src/parse/query.rs index 23d6c45d..bf366b8e 100644 --- a/src/parse/query.rs +++ b/src/parse/query.rs @@ -10,7 +10,7 @@ use miette::{bail, ensure, miette, Diagnostic, LabeledSpan, Report, Result}; use smartstring::{LazyCompact, SmartString}; use thiserror::Error; -use crate::algo::AlgoHandle; +use crate::algo::{AlgoHandle, AlgoNotFoundError}; use crate::data::aggr::{parse_aggr, Aggregation}; use crate::data::expr::Expr; use crate::data::id::Validity; @@ -581,6 +581,7 @@ fn parse_algo_rule( match nxt.as_rule() { Rule::algo_rel => { let inner = nxt.into_inner().next().unwrap(); + let span = inner.extract_span(); match inner.as_rule() { Rule::algo_rule_rel => { let mut els = inner.into_inner(); @@ -591,6 +592,7 @@ fn parse_algo_rule( rule_args.push(AlgoRuleArg::InMem { name: Symbol::new(name.as_str(), name.extract_span()), bindings, + span }) } Rule::algo_relation_rel => { @@ -605,6 +607,7 @@ fn parse_algo_rule( name.extract_span(), ), bindings, + span }) } Rule::algo_triple_rel => { @@ -628,6 +631,7 @@ fn parse_algo_rule( Symbol::new(snd.as_str(), snd.extract_span()), ], dir, + span }); } _ => unreachable!(), @@ -647,7 +651,7 @@ fn parse_algo_rule( let algo = AlgoHandle::new(algo_name, name_pair.extract_span()); let algo_arity = algo .arity(Left(&rule_args), &options) - .ok_or_else(|| miette!("bad algo arity"))?; + .ok_or_else(|| AlgoNotFoundError(algo.name.to_string(), algo.name.span))?; ensure!( head.is_empty() || algo_arity == head.len(), "algo head must have the same length as the return, or be omitted" diff --git a/src/query/compile.rs b/src/query/compile.rs index 0a03d95b..cd39d4bd 100644 --- a/src/query/compile.rs +++ b/src/query/compile.rs @@ -3,6 +3,7 @@ use std::collections::{BTreeMap, BTreeSet}; use itertools::Itertools; use miette::{ensure, miette, Context, Result}; +use crate::algo::AlgoNotFoundError; use crate::data::aggr::Aggregation; use crate::data::expr::Expr; use crate::data::program::{ @@ -96,7 +97,9 @@ impl SessionTx { name.clone(), self.new_rule_store( name.clone(), - ruleset.arity().ok_or_else(|| miette!("bad algo arity"))?, + ruleset.arity().ok_or_else(|| { + AlgoNotFoundError(name.symbol().to_string(), name.symbol().span) + })?, ), ); } @@ -366,21 +369,27 @@ impl SessionTx { MagicAtom::Unification(u) => { if seen_variables.contains(&u.binding) { let expr = if u.one_many_unif { - Expr::build_is_in(vec![ - Expr::Binding { - var: u.binding.clone(), - tuple_pos: None, - }, - u.expr.clone(), - ], u.span) + Expr::build_is_in( + vec![ + Expr::Binding { + var: u.binding.clone(), + tuple_pos: None, + }, + u.expr.clone(), + ], + u.span, + ) } else { - Expr::build_equate(vec![ - Expr::Binding { - var: u.binding.clone(), - tuple_pos: None, - }, - u.expr.clone(), - ], u.span) + Expr::build_equate( + vec![ + Expr::Binding { + var: u.binding.clone(), + tuple_pos: None, + }, + u.expr.clone(), + ], + u.span, + ) }; if let Some(fs) = ret.get_filters() { fs.push(expr); diff --git a/src/query/magic.rs b/src/query/magic.rs index 61f3ad50..ebb7af03 100644 --- a/src/query/magic.rs +++ b/src/query/magic.rs @@ -325,24 +325,31 @@ impl NormalFormProgram { .iter() .map(|r| -> Result { Ok(match r { - AlgoRuleArg::InMem { name, bindings } => { - MagicAlgoRuleArg::InMem { - name: MagicSymbol::Muggle { - inner: name.clone(), - }, - bindings: bindings.clone(), - } - } - AlgoRuleArg::Stored { name, bindings } => { - MagicAlgoRuleArg::Stored { - name: name.clone(), - bindings: bindings.clone(), - } - } + AlgoRuleArg::InMem { + name, + bindings, + span, + } => MagicAlgoRuleArg::InMem { + name: MagicSymbol::Muggle { + inner: name.clone(), + }, + bindings: bindings.clone(), + span: *span, + }, + AlgoRuleArg::Stored { + name, + bindings, + span, + } => MagicAlgoRuleArg::Stored { + name: name.clone(), + bindings: bindings.clone(), + span: *span, + }, AlgoRuleArg::Triple { name, bindings, dir, + span, } => { let attr = tx .attr_by_name(&name.name)? @@ -350,10 +357,11 @@ impl NormalFormProgram { miette!("cannot find attribute {}", name) })?; MagicAlgoRuleArg::Triple { - name: attr, + attr: attr, bindings: bindings.clone(), dir: *dir, vld: algo_apply.vld.unwrap_or(default_vld), + span: *span, } } })