datafox 0.1.0

A small Datalog parser and streaming query engine for querying facts.
Documentation
#![forbid(unsafe_code)]
//! Datafox is a standalone Datalog parser and streaming query engine for facts.
//!
//! The crate is intentionally small: callers provide facts through [`Storage`],
//! parse read-only queries with [`parse_query`] or [`parse_queries`], and evaluate
//! them with a [`DatafoxClient`] configured for the runtime profile you need.
//!
//! ```
//! use datafox::{DatafoxClient, DatafoxConfig, InMemoryStorage, Value, parse_query};
//!
//! let storage = InMemoryStorage::from_facts([(
//!     "edge".to_string(),
//!     vec![
//!         vec![Value::integer(1), Value::integer(2)],
//!         vec![Value::integer(2), Value::integer(3)],
//!     ],
//! )]);
//! let query = parse_query("edge(From, 2)")?;
//! let datafox = DatafoxClient::new(DatafoxConfig::new(&storage))?;
//! let results = datafox.eval(&query)?.collect::<Vec<_>>();
//!
//! assert_eq!(results.len(), 1);
//! assert_eq!(results[0].lookup("From"), Some(&Value::integer(1)));
//! # Ok::<(), datafox::Error>(())
//! ```
//!
//! Public API:
//! - [`Value`] for Datalog constants.
//! - [`Term`] for variables, constants, and wildcards.
//! - [`Atom`], [`Clause`], and [`Query`] for query syntax trees.
//! - [`Diagnostic`] and [`parse_query`] for query parsing with context.
//! - [`format_query`] and [`format_queries`] for stable, readable query files.
//! - [`Substitution`] and [`Unifier`] for binding and matching query variables.
//! - [`DatafoxClient`], [`DatafoxConfig`], [`DatafoxEnvironment`],
//!   [`PreparedQuery`], [`PreparedQueryStorage`], [`Planner`], [`Plan`], and [`Storage`]
//!   for snapshot-based query execution.
//! - [`Prelude`], [`BinaryRelation`], and [`BinaryOperator`] for ambient facts,
//!   builtin relations, and expression operators.
//! - [`Error`] and [`Result`] for typed failures.
//! - [`atom!`], [`var!`], [`lit!`], and [`subst!`] for test and call-site ergonomics.

mod ast;
mod client;
mod diagnostic;
pub mod error;
mod evaluator;
mod parser;
mod plan;
mod prelude;
mod pretty;
mod storage;
mod substitution;
mod term;
mod unify;
mod universe;
mod value;

pub use ast::{Atom, Clause, Query};
pub use client::{
    DatafoxClient, DatafoxConfig, DatafoxEnvironment, DatafoxEnvironmentBuilder,
    InMemoryPreparedQueryStorage, PlanningCache, PreparedQueryKey, PreparedQueryStorage,
};
pub use diagnostic::{Diagnostic, Span};
pub use error::{Error, Result};
pub use evaluator::{Evaluation, EvaluationStrategy};
pub use parser::{parse_queries, parse_query};
pub use plan::{PREPARED_QUERY_FORMAT_VERSION, Plan, Planner, PreparedQuery};
pub use prelude::{BinaryOperator, BinaryRelation, OperatorOutcome, Prelude, RelationOutcome};
pub use pretty::{format_queries, format_query};
pub use storage::{
    AtomRole, FactEstimate, FactRequest, FactRequestHints, FactRequestMode, FactScan, FactStore,
    FactTuple, InMemoryStorage, PatternValue, Projection, SnapshotSelector, Storage, TupleStream,
    matches_pattern,
};
pub use substitution::Substitution;
pub use term::Term;
pub use unify::Unifier;
pub use universe::Universe;
pub use value::Value;

#[macro_export]
macro_rules! atom {
    ($name:expr, $args:expr) => {{ $crate::Atom::new($name, $args).expect("invalid atom") }};
}

#[macro_export]
macro_rules! var {
    ($name:expr) => {{ $crate::Term::variable($name).expect("invalid variable") }};
}

#[macro_export]
macro_rules! lit {
    ($value:expr) => {{ $crate::Term::constant($value) }};
}

#[macro_export]
macro_rules! subst {
    ($(($name:expr, $value:expr)),* $(,)?) => {{
        $crate::Substitution::from_bindings(vec![
            $(($name.to_string(), $value)),*
        ])
    }};
}

#[cfg(test)]
mod tests {
    use crate::{Atom, Substitution, Term, Value};

    #[test]
    fn convenience_macros_build_terms_atoms_and_substitutions() {
        let atom = atom!(
            "spotify:displayName",
            vec![crate::var!("Album"), crate::lit!(Value::string("2112"))]
        );
        let substitution = crate::subst![
            ("Album", Value::string("spotify:album:2112")),
            ("Name", Value::string("2112")),
        ];

        assert_eq!(
            atom,
            Atom::new(
                "spotify:displayName",
                vec![
                    Term::variable("Album").expect("variable"),
                    Term::constant(Value::string("2112")),
                ],
            )
            .expect("atom"),
        );
        assert_eq!(
            substitution,
            Substitution::from_bindings(vec![
                ("Album".to_string(), Value::string("spotify:album:2112")),
                ("Name".to_string(), Value::string("2112")),
            ]),
        );
    }
}