cozo_ce/fixed_rule/
mod.rs

1/*
2 * Copyright 2022, The Cozo Project Authors.
3 *
4 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
5 * If a copy of the MPL was not distributed with this file,
6 * You can obtain one at https://mozilla.org/MPL/2.0/.
7 */
8
9use std::collections::BTreeMap;
10use std::sync::Arc;
11
12use crossbeam::channel::{bounded, Receiver, Sender};
13#[allow(unused_imports)]
14use either::{Left, Right};
15#[cfg(feature = "graph-algo")]
16use graph::prelude::{CsrLayout, DirectedCsrGraph, GraphBuilder};
17use itertools::Itertools;
18use lazy_static::lazy_static;
19use miette::IntoDiagnostic;
20#[allow(unused_imports)]
21use miette::{bail, ensure, Diagnostic, Report, Result};
22use smartstring::{LazyCompact, SmartString};
23use thiserror::Error;
24
25use crate::data::expr::Expr;
26use crate::data::program::{
27    FixedRuleOptionNotFoundError, MagicFixedRuleApply, MagicFixedRuleRuleArg, MagicSymbol,
28    WrongFixedRuleOptionError,
29};
30use crate::data::symb::Symbol;
31use crate::data::tuple::TupleIter;
32use crate::data::value::DataValue;
33#[cfg(feature = "graph-algo")]
34use crate::fixed_rule::algos::*;
35use crate::fixed_rule::utilities::*;
36use crate::parse::SourceSpan;
37use crate::runtime::db::Poison;
38use crate::runtime::temp_store::{EpochStore, RegularTempStore};
39use crate::runtime::transact::SessionTx;
40use crate::NamedRows;
41
42#[cfg(feature = "graph-algo")]
43pub(crate) mod algos;
44pub(crate) mod utilities;
45
46/// Passed into implementation of fixed rule, can be used to obtain relation inputs and options
47pub struct FixedRulePayload<'a, 'b> {
48    pub(crate) manifest: &'a MagicFixedRuleApply,
49    pub(crate) stores: &'a BTreeMap<MagicSymbol, EpochStore>,
50    pub(crate) tx: &'a SessionTx<'b>,
51}
52
53/// Represents an input relation during the execution of a fixed rule
54#[derive(Copy, Clone)]
55pub struct FixedRuleInputRelation<'a, 'b> {
56    arg_manifest: &'a MagicFixedRuleRuleArg,
57    stores: &'a BTreeMap<MagicSymbol, EpochStore>,
58    tx: &'a SessionTx<'b>,
59}
60
61impl<'a, 'b> FixedRuleInputRelation<'a, 'b> {
62    /// The arity of the input relation
63    pub fn arity(&self) -> Result<usize> {
64        self.arg_manifest.arity(self.tx, self.stores)
65    }
66    /// Ensure the input relation contains tuples of the given minimal length.
67    pub fn ensure_min_len(self, len: usize) -> Result<Self> {
68        #[derive(Error, Diagnostic, Debug)]
69        #[error("Input relation to algorithm has insufficient arity")]
70        #[diagnostic(help("Arity should be at least {0} but is {1}"))]
71        #[diagnostic(code(algo::input_relation_bad_arity))]
72        struct InputRelationArityError(usize, usize, #[label] SourceSpan);
73
74        let arity = self.arg_manifest.arity(self.tx, self.stores)?;
75        ensure!(
76            arity >= len,
77            InputRelationArityError(len, arity, self.arg_manifest.span())
78        );
79        Ok(self)
80    }
81    /// Get the binding map of the input relation
82    pub fn get_binding_map(&self, offset: usize) -> BTreeMap<Symbol, usize> {
83        self.arg_manifest.get_binding_map(offset)
84    }
85    /// Iterate the input relation
86    pub fn iter(&self) -> Result<TupleIter<'a>> {
87        Ok(match &self.arg_manifest {
88            MagicFixedRuleRuleArg::InMem { name, .. } => {
89                let store = self.stores.get(name).ok_or_else(|| {
90                    RuleNotFoundError(name.symbol().to_string(), name.symbol().span)
91                })?;
92                Box::new(store.all_iter().map(|t| Ok(t.into_tuple())))
93            }
94            MagicFixedRuleRuleArg::Stored { name, valid_at, .. } => {
95                let relation = self.tx.get_relation(name, false)?;
96                if let Some(valid_at) = valid_at {
97                    Box::new(relation.skip_scan_all(self.tx, *valid_at))
98                } else {
99                    Box::new(relation.scan_all(self.tx))
100                }
101            }
102        })
103    }
104    /// Iterate the relation with the given single-value prefix
105    pub fn prefix_iter(&self, prefix: &DataValue) -> Result<TupleIter<'_>> {
106        Ok(match self.arg_manifest {
107            MagicFixedRuleRuleArg::InMem { name, .. } => {
108                let store = self.stores.get(name).ok_or_else(|| {
109                    RuleNotFoundError(name.symbol().to_string(), name.symbol().span)
110                })?;
111                let t = vec![prefix.clone()];
112                Box::new(store.prefix_iter(&t).map(|t| Ok(t.into_tuple())))
113            }
114            MagicFixedRuleRuleArg::Stored { name, valid_at, .. } => {
115                let relation = self.tx.get_relation(name, false)?;
116                let t = vec![prefix.clone()];
117                if let Some(valid_at) = valid_at {
118                    Box::new(relation.skip_scan_prefix(self.tx, &t, *valid_at))
119                } else {
120                    Box::new(relation.scan_prefix(self.tx, &t))
121                }
122            }
123        })
124    }
125    /// Get the source span of the input relation. Useful for generating informative error messages.
126    pub fn span(&self) -> SourceSpan {
127        self.arg_manifest.span()
128    }
129    /// Convert the input relation into a directed graph.
130    /// If `undirected` is true, then each edge in the input relation is treated as a pair
131    /// of edges, one for each direction.
132    ///
133    /// Returns the graph, the vertices in a vector with the index the same as used in the graph,
134    /// and the inverse vertex mapping.
135    #[cfg(feature = "graph-algo")]
136    pub fn as_directed_graph(
137        &self,
138        undirected: bool,
139    ) -> Result<(
140        DirectedCsrGraph<u32>,
141        Vec<DataValue>,
142        BTreeMap<DataValue, u32>,
143    )> {
144        let mut indices: Vec<DataValue> = vec![];
145        let mut inv_indices: BTreeMap<DataValue, u32> = Default::default();
146        let mut error: Option<Report> = None;
147        let it = self.iter()?.filter_map(|r_tuple| match r_tuple {
148            Ok(tuple) => {
149                let mut tuple = tuple.into_iter();
150                let from = match tuple.next() {
151                    None => {
152                        error = Some(NotAnEdgeError(self.span()).into());
153                        return None;
154                    }
155                    Some(f) => f,
156                };
157                let to = match tuple.next() {
158                    None => {
159                        error = Some(NotAnEdgeError(self.span()).into());
160                        return None;
161                    }
162                    Some(f) => f,
163                };
164                let from_idx = if let Some(idx) = inv_indices.get(&from) {
165                    *idx
166                } else {
167                    let idx = indices.len() as u32;
168                    inv_indices.insert(from.clone(), idx);
169                    indices.push(from.clone());
170                    idx
171                };
172                let to_idx = if let Some(idx) = inv_indices.get(&to) {
173                    *idx
174                } else {
175                    let idx = indices.len() as u32;
176                    inv_indices.insert(to.clone(), idx);
177                    indices.push(to.clone());
178                    idx
179                };
180                Some((from_idx, to_idx))
181            }
182            Err(err) => {
183                error = Some(err);
184                None
185            }
186        });
187        let it = if undirected {
188            Right(it.flat_map(|(f, t)| [(f, t), (t, f)]))
189        } else {
190            Left(it)
191        };
192        let graph: DirectedCsrGraph<u32> = GraphBuilder::new()
193            .csr_layout(CsrLayout::Sorted)
194            .edges(it)
195            .build();
196        if let Some(err) = error {
197            bail!(err)
198        }
199        Ok((graph, indices, inv_indices))
200    }
201    /// Convert the input relation into a directed weighted graph.
202    /// If `undirected` is true, then each edge in the input relation is treated as a pair
203    /// of edges, one for each direction.
204    ///
205    /// Returns the graph, the vertices in a vector with the index the same as used in the graph,
206    /// and the inverse vertex mapping.
207    #[cfg(feature = "graph-algo")]
208    pub fn as_directed_weighted_graph(
209        &self,
210        undirected: bool,
211        allow_negative_weights: bool,
212    ) -> Result<(
213        DirectedCsrGraph<u32, (), f32>,
214        Vec<DataValue>,
215        BTreeMap<DataValue, u32>,
216    )> {
217        let mut indices: Vec<DataValue> = vec![];
218        let mut inv_indices: BTreeMap<DataValue, u32> = Default::default();
219        let mut error: Option<Report> = None;
220        let it = self.iter()?.filter_map(|r_tuple| match r_tuple {
221            Ok(tuple) => {
222                let mut tuple = tuple.into_iter();
223                let from = match tuple.next() {
224                    None => {
225                        error = Some(NotAnEdgeError(self.span()).into());
226                        return None;
227                    }
228                    Some(f) => f,
229                };
230                let to = match tuple.next() {
231                    None => {
232                        error = Some(NotAnEdgeError(self.span()).into());
233                        return None;
234                    }
235                    Some(f) => f,
236                };
237                let from_idx = if let Some(idx) = inv_indices.get(&from) {
238                    *idx
239                } else {
240                    let idx = indices.len() as u32;
241                    inv_indices.insert(from.clone(), idx);
242                    indices.push(from.clone());
243                    idx
244                };
245                let to_idx = if let Some(idx) = inv_indices.get(&to) {
246                    *idx
247                } else {
248                    let idx = indices.len() as u32;
249                    inv_indices.insert(to.clone(), idx);
250                    indices.push(to.clone());
251                    idx
252                };
253
254                let weight = match tuple.next() {
255                    None => 1.0,
256                    Some(d) => match d.get_float() {
257                        Some(f) => {
258                            if !f.is_finite() {
259                                error = Some(
260                                    BadEdgeWeightError(
261                                        d,
262                                        self.arg_manifest
263                                            .bindings()
264                                            .get(2)
265                                            .map(|s| s.span)
266                                            .unwrap_or_else(|| self.span()),
267                                    )
268                                    .into(),
269                                );
270                                return None;
271                            };
272
273                            if f < 0. && !allow_negative_weights {
274                                error = Some(
275                                    BadEdgeWeightError(
276                                        d,
277                                        self.arg_manifest
278                                            .bindings()
279                                            .get(2)
280                                            .map(|s| s.span)
281                                            .unwrap_or_else(|| self.span()),
282                                    )
283                                    .into(),
284                                );
285                                return None;
286                            }
287                            f
288                        }
289                        None => {
290                            error = Some(
291                                BadEdgeWeightError(
292                                    d,
293                                    self.arg_manifest
294                                        .bindings()
295                                        .get(2)
296                                        .map(|s| s.span)
297                                        .unwrap_or_else(|| self.span()),
298                                )
299                                .into(),
300                            );
301                            return None;
302                        }
303                    },
304                };
305
306                Some((from_idx, to_idx, weight as f32))
307            }
308            Err(err) => {
309                error = Some(err);
310                None
311            }
312        });
313        let it = if undirected {
314            Right(it.flat_map(|(f, t, w)| [(f, t, w), (t, f, w)]))
315        } else {
316            Left(it)
317        };
318        let graph: DirectedCsrGraph<u32, (), f32> = GraphBuilder::new()
319            .csr_layout(CsrLayout::Sorted)
320            .edges_with_values(it)
321            .build();
322
323        if let Some(err) = error {
324            bail!(err)
325        }
326
327        Ok((graph, indices, inv_indices))
328    }
329}
330
331impl<'a, 'b> FixedRulePayload<'a, 'b> {
332    /// Get the total number of input relations.
333    pub fn inputs_count(&self) -> usize {
334        self.manifest.relations_count()
335    }
336    /// Get the input relation at `idx`.
337    pub fn get_input(&self, idx: usize) -> Result<FixedRuleInputRelation<'a, 'b>> {
338        let arg_manifest = self.manifest.relation(idx)?;
339        Ok(FixedRuleInputRelation {
340            arg_manifest,
341            stores: self.stores,
342            tx: self.tx,
343        })
344    }
345    /// Get the name of the current fixed rule
346    pub fn name(&self) -> &str {
347        &self.manifest.fixed_handle.name
348    }
349    /// Get the source span of the payloads. Useful for generating informative errors.
350    pub fn span(&self) -> SourceSpan {
351        self.manifest.span
352    }
353    /// Extract an expression option
354    pub fn expr_option(&self, name: &str, default: Option<Expr>) -> Result<Expr> {
355        match self.manifest.options.get(name) {
356            Some(ex) => Ok(ex.clone()),
357            None => match default {
358                Some(ex) => Ok(ex),
359                None => Err(FixedRuleOptionNotFoundError {
360                    name: name.to_string(),
361                    span: self.manifest.span,
362                    rule_name: self.manifest.fixed_handle.name.to_string(),
363                }
364                .into()),
365            },
366        }
367    }
368
369    /// Extract a string option
370    pub fn string_option(
371        &self,
372        name: &str,
373        default: Option<&str>,
374    ) -> Result<SmartString<LazyCompact>> {
375        match self.manifest.options.get(name) {
376            Some(ex) => match ex.clone().eval_to_const()? {
377                DataValue::Str(s) => Ok(s),
378                _ => Err(WrongFixedRuleOptionError {
379                    name: name.to_string(),
380                    span: ex.span(),
381                    rule_name: self.manifest.fixed_handle.name.to_string(),
382                    help: "a string is required".to_string(),
383                }
384                .into()),
385            },
386            None => match default {
387                None => Err(FixedRuleOptionNotFoundError {
388                    name: name.to_string(),
389                    span: self.manifest.span,
390                    rule_name: self.manifest.fixed_handle.name.to_string(),
391                }
392                .into()),
393                Some(s) => Ok(SmartString::from(s)),
394            },
395        }
396    }
397
398    /// Get the source span of the named option. Useful for generating informative error messages.
399    pub fn option_span(&self, name: &str) -> Result<SourceSpan> {
400        match self.manifest.options.get(name) {
401            None => Err(FixedRuleOptionNotFoundError {
402                name: name.to_string(),
403                span: self.manifest.span,
404                rule_name: self.manifest.fixed_handle.name.to_string(),
405            }
406            .into()),
407            Some(v) => Ok(v.span()),
408        }
409    }
410    /// Extract an integer option
411    pub fn integer_option(&self, name: &str, default: Option<i64>) -> Result<i64> {
412        match self.manifest.options.get(name) {
413            Some(v) => match v.clone().eval_to_const() {
414                Ok(DataValue::Num(n)) => match n.get_int() {
415                    Some(i) => Ok(i),
416                    None => Err(FixedRuleOptionNotFoundError {
417                        name: name.to_string(),
418                        span: self.manifest.span,
419                        rule_name: self.manifest.fixed_handle.name.to_string(),
420                    }
421                    .into()),
422                },
423                _ => Err(WrongFixedRuleOptionError {
424                    name: name.to_string(),
425                    span: v.span(),
426                    rule_name: self.manifest.fixed_handle.name.to_string(),
427                    help: "an integer is required".to_string(),
428                }
429                .into()),
430            },
431            None => match default {
432                Some(v) => Ok(v),
433                None => Err(FixedRuleOptionNotFoundError {
434                    name: name.to_string(),
435                    span: self.manifest.span,
436                    rule_name: self.manifest.fixed_handle.name.to_string(),
437                }
438                .into()),
439            },
440        }
441    }
442    /// Extract a positive integer option
443    pub fn pos_integer_option(&self, name: &str, default: Option<usize>) -> Result<usize> {
444        let i = self.integer_option(name, default.map(|i| i as i64))?;
445        ensure!(
446            i > 0,
447            WrongFixedRuleOptionError {
448                name: name.to_string(),
449                span: self.option_span(name)?,
450                rule_name: self.manifest.fixed_handle.name.to_string(),
451                help: "a positive integer is required".to_string(),
452            }
453        );
454        Ok(i as usize)
455    }
456    /// Extract a non-negative integer option
457    pub fn non_neg_integer_option(&self, name: &str, default: Option<usize>) -> Result<usize> {
458        let i = self.integer_option(name, default.map(|i| i as i64))?;
459        ensure!(
460            i >= 0,
461            WrongFixedRuleOptionError {
462                name: name.to_string(),
463                span: self.option_span(name)?,
464                rule_name: self.manifest.fixed_handle.name.to_string(),
465                help: "a non-negative integer is required".to_string(),
466            }
467        );
468        Ok(i as usize)
469    }
470    /// Extract a floating point option
471    pub fn float_option(&self, name: &str, default: Option<f64>) -> Result<f64> {
472        match self.manifest.options.get(name) {
473            Some(v) => match v.clone().eval_to_const() {
474                Ok(DataValue::Num(n)) => {
475                    let f = n.get_float();
476                    Ok(f)
477                }
478                _ => Err(WrongFixedRuleOptionError {
479                    name: name.to_string(),
480                    span: v.span(),
481                    rule_name: self.manifest.fixed_handle.name.to_string(),
482                    help: "a floating number is required".to_string(),
483                }
484                .into()),
485            },
486            None => match default {
487                Some(v) => Ok(v),
488                None => Err(FixedRuleOptionNotFoundError {
489                    name: name.to_string(),
490                    span: self.manifest.span,
491                    rule_name: self.manifest.fixed_handle.name.to_string(),
492                }
493                .into()),
494            },
495        }
496    }
497    /// Extract a floating point option between 0. and 1.
498    pub fn unit_interval_option(&self, name: &str, default: Option<f64>) -> Result<f64> {
499        let f = self.float_option(name, default)?;
500        ensure!(
501            (0. ..=1.).contains(&f),
502            WrongFixedRuleOptionError {
503                name: name.to_string(),
504                span: self.option_span(name)?,
505                rule_name: self.manifest.fixed_handle.name.to_string(),
506                help: "a number between 0. and 1. is required".to_string(),
507            }
508        );
509        Ok(f)
510    }
511    /// Extract a boolean option
512    pub fn bool_option(&self, name: &str, default: Option<bool>) -> Result<bool> {
513        match self.manifest.options.get(name) {
514            Some(v) => match v.clone().eval_to_const() {
515                Ok(DataValue::Bool(b)) => Ok(b),
516                _ => Err(WrongFixedRuleOptionError {
517                    name: name.to_string(),
518                    span: v.span(),
519                    rule_name: self.manifest.fixed_handle.name.to_string(),
520                    help: "a boolean value is required".to_string(),
521                }
522                .into()),
523            },
524            None => match default {
525                Some(v) => Ok(v),
526                None => Err(FixedRuleOptionNotFoundError {
527                    name: name.to_string(),
528                    span: self.manifest.span,
529                    rule_name: self.manifest.fixed_handle.name.to_string(),
530                }
531                .into()),
532            },
533        }
534    }
535}
536
537/// Trait for an implementation of an algorithm or a utility
538pub trait FixedRule: Send + Sync {
539    /// Called to initialize the options given.
540    /// Will always be called once, before anything else.
541    /// You can mutate the options if you need to.
542    /// The default implementation does nothing.
543    fn init_options(
544        &self,
545        _options: &mut BTreeMap<SmartString<LazyCompact>, Expr>,
546        _span: SourceSpan,
547    ) -> Result<()> {
548        Ok(())
549    }
550    /// You must return the row width of the returned relation and it must be accurate.
551    /// This function may be called multiple times.
552    fn arity(
553        &self,
554        options: &BTreeMap<SmartString<LazyCompact>, Expr>,
555        rule_head: &[Symbol],
556        span: SourceSpan,
557    ) -> Result<usize>;
558    /// You should implement the logic of your algorithm/utility in this function.
559    /// The outputs are written to `out`. You should check `poison` periodically
560    /// for user-initiated termination.
561    fn run(
562        &self,
563        payload: FixedRulePayload<'_, '_>,
564        out: &'_ mut RegularTempStore,
565        poison: Poison,
566    ) -> Result<()>;
567}
568
569/// Simple wrapper for custom fixed rule. You have less control than implementing [FixedRule] directly,
570/// but implementation is simpler.
571pub struct SimpleFixedRule {
572    return_arity: usize,
573    rule: Box<
574        dyn Fn(Vec<NamedRows>, BTreeMap<String, DataValue>) -> Result<NamedRows>
575            + Send
576            + Sync
577            + 'static,
578    >,
579}
580
581impl SimpleFixedRule {
582    /// Construct a SimpleFixedRule.
583    ///
584    /// * `return_arity`: The return arity of this rule.
585    /// * `rule`:  The rule implementation as a closure.
586    //    The first argument is a vector of input relations, realized into NamedRows,
587    //    and the second argument is a JSON object of passed in options.
588    //    The returned NamedRows is the return relation of the application of this rule.
589    //    Every row of the returned relation must have length equal to `return_arity`.
590    pub fn new<R>(return_arity: usize, rule: R) -> Self
591    where
592        R: Fn(Vec<NamedRows>, BTreeMap<String, DataValue>) -> Result<NamedRows>
593            + Send
594            + Sync
595            + 'static,
596    {
597        Self {
598            return_arity,
599            rule: Box::new(rule),
600        }
601    }
602    /// Construct a SimpleFixedRule that uses channels for communication.
603    pub fn rule_with_channel(
604        return_arity: usize,
605    ) -> (
606        Self,
607        Receiver<(
608            Vec<NamedRows>,
609            BTreeMap<String, DataValue>,
610            Sender<Result<NamedRows>>,
611        )>,
612    ) {
613        let (db2app_sender, db2app_receiver) = bounded(0);
614        (
615            Self {
616                return_arity,
617                rule: Box::new(move |inputs, options| -> Result<NamedRows> {
618                    let (app2db_sender, app2db_receiver) = bounded(0);
619                    db2app_sender
620                        .send((inputs, options, app2db_sender))
621                        .into_diagnostic()?;
622                    app2db_receiver.recv().into_diagnostic()?
623                }),
624            },
625            db2app_receiver,
626        )
627    }
628}
629
630impl FixedRule for SimpleFixedRule {
631    fn arity(
632        &self,
633        _options: &BTreeMap<SmartString<LazyCompact>, Expr>,
634        _rule_head: &[Symbol],
635        _span: SourceSpan,
636    ) -> Result<usize> {
637        Ok(self.return_arity)
638    }
639
640    fn run(
641        &self,
642        payload: FixedRulePayload<'_, '_>,
643        out: &'_ mut RegularTempStore,
644        _poison: Poison,
645    ) -> Result<()> {
646        let options: BTreeMap<_, _> = payload
647            .manifest
648            .options
649            .iter()
650            .map(|(k, v)| -> Result<_> {
651                let val = v.clone().eval_to_const()?;
652                Ok((k.to_string(), val))
653            })
654            .try_collect()?;
655        let input_arity = payload.manifest.rule_args.len();
656        let inputs: Vec<_> = (0..input_arity)
657            .map(|i| -> Result<_> {
658                let input = payload.get_input(i).unwrap();
659                let rows: Vec<_> = input.iter()?.try_collect()?;
660                let mut headers = input
661                    .arg_manifest
662                    .bindings()
663                    .iter()
664                    .map(|s| s.name.to_string())
665                    .collect_vec();
666                let l = headers.len();
667                let m = input.arg_manifest.arity(payload.tx, payload.stores)?;
668                for i in l..m {
669                    headers.push(format!("_{i}"));
670                }
671                Ok(NamedRows::new(headers, rows))
672            })
673            .try_collect()?;
674        let results: NamedRows = (self.rule)(inputs, options)?;
675        for row in results.rows {
676            #[derive(Debug, Error, Diagnostic)]
677            #[error("arity mismatch: expect {1}, got {2}")]
678            #[diagnostic(code(parser::simple_fixed_rule_arity_mismatch))]
679            struct ArityMismatch(#[label] SourceSpan, usize, usize);
680
681            ensure!(
682                row.len() == self.return_arity,
683                ArityMismatch(payload.span(), self.return_arity, row.len())
684            );
685            out.put(row);
686        }
687        Ok(())
688    }
689}
690
691#[derive(Debug, Error, Diagnostic)]
692#[error("Cannot determine arity for algo {0} since {1}")]
693#[diagnostic(code(parser::no_algo_arity))]
694pub(crate) struct CannotDetermineArity(
695    pub(crate) String,
696    pub(crate) String,
697    #[label] pub(crate) SourceSpan,
698);
699
700#[derive(Clone, Debug)]
701pub(crate) struct FixedRuleHandle {
702    pub(crate) name: Symbol,
703}
704
705lazy_static! {
706    pub(crate) static ref DEFAULT_FIXED_RULES: BTreeMap<String, Arc<Box<dyn FixedRule>>> = {
707        BTreeMap::from([
708            #[cfg(feature = "graph-algo")]
709            (
710                "ClusteringCoefficients".to_string(),
711                Arc::<Box<dyn FixedRule>>::new(Box::new(ClusteringCoefficients)),
712            ),
713            #[cfg(feature = "graph-algo")]
714            (
715                "DegreeCentrality".to_string(),
716                Arc::<Box<dyn FixedRule>>::new(Box::new(DegreeCentrality)),
717            ),
718            #[cfg(feature = "graph-algo")]
719            (
720                "ClosenessCentrality".to_string(),
721                Arc::<Box<dyn FixedRule>>::new(Box::new(ClosenessCentrality)),
722            ),
723            #[cfg(feature = "graph-algo")]
724            (
725                "BetweennessCentrality".to_string(),
726                Arc::<Box<dyn FixedRule>>::new(Box::new(BetweennessCentrality)),
727            ),
728            #[cfg(feature = "graph-algo")]
729            (
730                "DepthFirstSearch".to_string(),
731                Arc::<Box<dyn FixedRule>>::new(Box::new(Dfs)),
732            ),
733            #[cfg(feature = "graph-algo")]
734            (
735                "DFS".to_string(),
736                Arc::<Box<dyn FixedRule>>::new(Box::new(Dfs)),
737            ),
738            #[cfg(feature = "graph-algo")]
739            (
740                "BreadthFirstSearch".to_string(),
741                Arc::<Box<dyn FixedRule>>::new(Box::new(Bfs)),
742            ),
743            #[cfg(feature = "graph-algo")]
744            (
745                "BFS".to_string(),
746                Arc::<Box<dyn FixedRule>>::new(Box::new(Bfs)),
747            ),
748            #[cfg(feature = "graph-algo")]
749            (
750                "ShortestPathBFS".to_string(),
751                Arc::<Box<dyn FixedRule>>::new(Box::new(ShortestPathBFS)),
752            ),
753            #[cfg(feature = "graph-algo")]
754            (
755                "ShortestPathDijkstra".to_string(),
756                Arc::<Box<dyn FixedRule>>::new(Box::new(ShortestPathDijkstra)),
757            ),
758            #[cfg(feature = "graph-algo")]
759            (
760                "ShortestPathAStar".to_string(),
761                Arc::<Box<dyn FixedRule>>::new(Box::new(ShortestPathAStar)),
762            ),
763            #[cfg(feature = "graph-algo")]
764            (
765                "KShortestPathYen".to_string(),
766                Arc::<Box<dyn FixedRule>>::new(Box::new(KShortestPathYen)),
767            ),
768            #[cfg(feature = "graph-algo")]
769            (
770                "MinimumSpanningTreePrim".to_string(),
771                Arc::<Box<dyn FixedRule>>::new(Box::new(MinimumSpanningTreePrim)),
772            ),
773            #[cfg(feature = "graph-algo")]
774            (
775                "MinimumSpanningForestKruskal".to_string(),
776                Arc::<Box<dyn FixedRule>>::new(Box::new(MinimumSpanningForestKruskal)),
777            ),
778            #[cfg(feature = "graph-algo")]
779            (
780                "TopSort".to_string(),
781                Arc::<Box<dyn FixedRule>>::new(Box::new(TopSort)),
782            ),
783            #[cfg(feature = "graph-algo")]
784            (
785                "ConnectedComponents".to_string(),
786                Arc::<Box<dyn FixedRule>>::new(Box::new(StronglyConnectedComponent::new(false))),
787            ),
788            #[cfg(feature = "graph-algo")]
789            (
790                "StronglyConnectedComponents".to_string(),
791                Arc::<Box<dyn FixedRule>>::new(Box::new(StronglyConnectedComponent::new(true))),
792            ),
793            #[cfg(feature = "graph-algo")]
794            (
795                "SCC".to_string(),
796                Arc::<Box<dyn FixedRule>>::new(Box::new(StronglyConnectedComponent::new(true))),
797            ),
798            #[cfg(feature = "graph-algo")]
799            (
800                "PageRank".to_string(),
801                Arc::<Box<dyn FixedRule>>::new(Box::new(PageRank)),
802            ),
803            #[cfg(feature = "graph-algo")]
804            (
805                "CommunityDetectionLouvain".to_string(),
806                Arc::<Box<dyn FixedRule>>::new(Box::new(CommunityDetectionLouvain)),
807            ),
808            #[cfg(feature = "graph-algo")]
809            (
810                "LabelPropagation".to_string(),
811                Arc::<Box<dyn FixedRule>>::new(Box::new(LabelPropagation)),
812            ),
813            #[cfg(feature = "graph-algo")]
814            (
815                "RandomWalk".to_string(),
816                Arc::<Box<dyn FixedRule>>::new(Box::new(RandomWalk)),
817            ),
818            (
819                "ReorderSort".to_string(),
820                Arc::<Box<dyn FixedRule>>::new(Box::new(ReorderSort)),
821            ),
822            (
823                "JsonReader".to_string(),
824                Arc::<Box<dyn FixedRule>>::new(Box::new(JsonReader)),
825            ),
826            (
827                "CsvReader".to_string(),
828                Arc::<Box<dyn FixedRule>>::new(Box::new(CsvReader)),
829            ),
830            (
831                "Constant".to_string(),
832                Arc::<Box<dyn FixedRule>>::new(Box::new(Constant)),
833            ),
834        ])
835    };
836}
837
838impl FixedRuleHandle {
839    pub(crate) fn new(name: &str, span: SourceSpan) -> Self {
840        FixedRuleHandle {
841            name: Symbol::new(name, span),
842        }
843    }
844}
845
846#[derive(Error, Diagnostic, Debug)]
847#[error("The relation cannot be interpreted as an edge")]
848#[diagnostic(code(algo::not_an_edge))]
849#[diagnostic(help("Edge relation requires tuples of length at least two"))]
850struct NotAnEdgeError(#[label] SourceSpan);
851
852#[derive(Error, Diagnostic, Debug)]
853#[error(
854    "The value {0:?} at the third position in the relation cannot be interpreted as edge weights"
855)]
856#[diagnostic(code(algo::invalid_edge_weight))]
857#[diagnostic(help(
858    "Edge weights must be finite numbers. Some algorithm also requires positivity."
859))]
860struct BadEdgeWeightError(DataValue, #[label] SourceSpan);
861
862#[derive(Error, Diagnostic, Debug)]
863#[error("The requested rule '{0}' cannot be found")]
864#[diagnostic(code(algo::rule_not_found))]
865struct RuleNotFoundError(String, #[label] SourceSpan);
866
867#[derive(Error, Diagnostic, Debug)]
868#[error("Invalid reverse scanning of triples")]
869#[diagnostic(code(algo::invalid_reverse_triple_scan))]
870#[diagnostic(help(
871    "Inverse scanning of triples requires the type to be 'ref', or the value be indexed"
872))]
873struct InvalidInverseTripleUse(String, #[label] SourceSpan);
874
875#[derive(Error, Diagnostic, Debug)]
876#[error("Required node with key {missing:?} not found")]
877#[diagnostic(code(algo::node_with_key_not_found))]
878#[diagnostic(help(
879    "The relation is interpreted as a relation of nodes, but the required key is missing"
880))]
881pub(crate) struct NodeNotFoundError {
882    pub(crate) missing: DataValue,
883    #[label]
884    pub(crate) span: SourceSpan,
885}
886
887#[derive(Error, Diagnostic, Debug)]
888#[error("Unacceptable value {0:?} encountered")]
889#[diagnostic(code(algo::unacceptable_value))]
890pub(crate) struct BadExprValueError(
891    pub(crate) DataValue,
892    #[label] pub(crate) SourceSpan,
893    #[help] pub(crate) String,
894);
895
896#[derive(Error, Diagnostic, Debug)]
897#[error("The requested fixed rule '{0}' is not found")]
898#[diagnostic(code(parser::fixed_rule_not_found))]
899pub(crate) struct FixedRuleNotFoundError(pub(crate) String, #[label] pub(crate) SourceSpan);
900
901impl MagicFixedRuleRuleArg {
902    pub(crate) fn arity(
903        &self,
904        tx: &SessionTx<'_>,
905        stores: &BTreeMap<MagicSymbol, EpochStore>,
906    ) -> Result<usize> {
907        Ok(match self {
908            MagicFixedRuleRuleArg::InMem { name, .. } => {
909                let store = stores.get(name).ok_or_else(|| {
910                    RuleNotFoundError(name.symbol().to_string(), name.symbol().span)
911                })?;
912                store.arity
913            }
914            MagicFixedRuleRuleArg::Stored { name, .. } => {
915                let handle = tx.get_relation(name, false)?;
916                handle.arity()
917            }
918        })
919    }
920}