pub mod constantconstraint;
pub mod equalityconstraint;
pub mod hashmapconstraint;
pub mod hashsetconstraint;
pub mod ignore;
pub mod intersectionconstraint;
pub mod patchconstraint;
pub mod rangeconstraint;
pub mod regularpathconstraint;
pub mod sortedsliceconstraint;
pub mod unionconstraint;
mod variableset;
use std::cmp::Reverse;
use std::fmt;
use std::iter::FromIterator;
use std::marker::PhantomData;
use arrayvec::ArrayVec;
use constantconstraint::*;
pub use ignore::IgnoreConstraint;
use crate::value::schemas::genid::GenId;
use crate::value::RawValue;
use crate::value::Value;
use crate::value::ValueSchema;
pub use regularpathconstraint::PathOp;
pub use regularpathconstraint::RegularPathConstraint;
pub use variableset::VariableSet;
pub trait TriblePattern {
type PatternConstraint<'a>: Constraint<'a>
where
Self: 'a;
fn pattern<'a, V: ValueSchema>(
&'a self,
e: Variable<GenId>,
a: Variable<GenId>,
v: Variable<V>,
) -> Self::PatternConstraint<'a>;
}
pub type VariableId = usize;
#[derive(Debug)]
pub struct VariableContext {
pub next_index: VariableId,
}
impl Default for VariableContext {
fn default() -> Self {
Self::new()
}
}
impl VariableContext {
pub fn new() -> Self {
VariableContext { next_index: 0 }
}
pub fn next_variable<T: ValueSchema>(&mut self) -> Variable<T> {
assert!(
self.next_index < 128,
"currently queries support at most 128 variables"
);
let v = Variable::new(self.next_index);
self.next_index += 1;
v
}
}
#[derive(Debug)]
pub struct Variable<T: ValueSchema> {
pub index: VariableId,
typed: PhantomData<T>,
}
impl<T: ValueSchema> Copy for Variable<T> {}
impl<T: ValueSchema> Clone for Variable<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: ValueSchema> Variable<T> {
pub fn new(index: VariableId) -> Self {
Variable {
index,
typed: PhantomData,
}
}
pub fn extract(self, binding: &Binding) -> &Value<T> {
let raw = binding.get(self.index).unwrap_or_else(|| {
panic!(
"query variable (idx {}) was never bound before projection. This usually means the variable was projected in `find!` but never appeared in any constraint. If you intended a pure existence query, use `find!((), ...)` or `exists!(constraint)`.",
self.index
)
});
Value::as_transmute_raw(raw)
}
}
pub trait ContainsConstraint<'a, T: ValueSchema> {
type Constraint: Constraint<'a>;
fn has(self, v: Variable<T>) -> Self::Constraint;
}
impl<T: ValueSchema> Variable<T> {
pub fn is(self, constant: Value<T>) -> ConstantConstraint {
ConstantConstraint::new(self, constant)
}
}
#[derive(Clone, Debug)]
pub struct Binding {
pub bound: VariableSet,
values: [RawValue; 128],
}
impl Binding {
pub fn set(&mut self, variable: VariableId, value: &RawValue) {
self.values[variable] = *value;
self.bound.set(variable);
}
pub fn unset(&mut self, variable: VariableId) {
self.bound.unset(variable);
}
pub fn get(&self, variable: VariableId) -> Option<&RawValue> {
if self.bound.is_set(variable) {
Some(&self.values[variable])
} else {
None
}
}
}
impl Default for Binding {
fn default() -> Self {
Self {
bound: VariableSet::new_empty(),
values: [[0; 32]; 128],
}
}
}
pub trait Constraint<'a> {
fn variables(&self) -> VariableSet;
fn estimate(&self, variable: VariableId, binding: &Binding) -> Option<usize>;
fn propose(&self, variable: VariableId, binding: &Binding, proposals: &mut Vec<RawValue>);
fn confirm(&self, variable: VariableId, binding: &Binding, proposals: &mut Vec<RawValue>);
fn satisfied(&self, _binding: &Binding) -> bool {
true
}
fn influence(&self, variable: VariableId) -> VariableSet {
let mut vars = self.variables();
if vars.is_set(variable) {
vars.unset(variable);
vars
} else {
VariableSet::new_empty()
}
}
}
impl<'a, T: Constraint<'a> + ?Sized> Constraint<'a> for Box<T> {
fn variables(&self) -> VariableSet {
let inner: &T = self;
inner.variables()
}
fn estimate(&self, variable: VariableId, binding: &Binding) -> Option<usize> {
let inner: &T = self;
inner.estimate(variable, binding)
}
fn propose(&self, variable: VariableId, binding: &Binding, proposals: &mut Vec<RawValue>) {
let inner: &T = self;
inner.propose(variable, binding, proposals)
}
fn confirm(&self, variable: VariableId, binding: &Binding, proposals: &mut Vec<RawValue>) {
let inner: &T = self;
inner.confirm(variable, binding, proposals)
}
fn satisfied(&self, binding: &Binding) -> bool {
let inner: &T = self;
inner.satisfied(binding)
}
fn influence(&self, variable: VariableId) -> VariableSet {
let inner: &T = self;
inner.influence(variable)
}
}
impl<'a, T: Constraint<'a> + ?Sized> Constraint<'static> for std::sync::Arc<T> {
fn variables(&self) -> VariableSet {
let inner: &T = self;
inner.variables()
}
fn estimate(&self, variable: VariableId, binding: &Binding) -> Option<usize> {
let inner: &T = self;
inner.estimate(variable, binding)
}
fn propose(&self, variable: VariableId, binding: &Binding, proposals: &mut Vec<RawValue>) {
let inner: &T = self;
inner.propose(variable, binding, proposals)
}
fn confirm(&self, variable: VariableId, binding: &Binding, proposal: &mut Vec<RawValue>) {
let inner: &T = self;
inner.confirm(variable, binding, proposal)
}
fn satisfied(&self, binding: &Binding) -> bool {
let inner: &T = self;
inner.satisfied(binding)
}
fn influence(&self, variable: VariableId) -> VariableSet {
let inner: &T = self;
inner.influence(variable)
}
}
pub struct Query<C, P: Fn(&Binding) -> Option<R>, R> {
constraint: C,
postprocessing: P,
mode: Search,
binding: Binding,
influences: [VariableSet; 128],
estimates: [usize; 128],
touched_variables: VariableSet,
stack: ArrayVec<VariableId, 128>,
unbound: ArrayVec<VariableId, 128>,
values: ArrayVec<Option<Vec<RawValue>>, 128>,
}
impl<'a, C: Constraint<'a>, P: Fn(&Binding) -> Option<R>, R> Query<C, P, R> {
pub fn new(constraint: C, postprocessing: P) -> Self {
let variables = constraint.variables();
let influences = std::array::from_fn(|v| {
if variables.is_set(v) {
constraint.influence(v)
} else {
VariableSet::new_empty()
}
});
let binding = Binding::default();
let estimates = std::array::from_fn(|v| {
if variables.is_set(v) {
constraint
.estimate(v, &binding)
.expect("unconstrained variable in query")
} else {
usize::MAX
}
});
let mut unbound = ArrayVec::from_iter(variables);
unbound.sort_unstable_by_key(|v| {
(
Reverse(
estimates[*v]
.checked_ilog2()
.map(|magnitude| magnitude + 1)
.unwrap_or(0),
),
influences[*v].count(),
)
});
Query {
constraint,
postprocessing,
mode: Search::NextVariable,
binding,
influences,
estimates,
touched_variables: VariableSet::new_empty(),
stack: ArrayVec::new(),
unbound,
values: ArrayVec::from([const { None }; 128]),
}
}
}
#[derive(Copy, Clone, Debug)]
enum Search {
NextVariable,
NextValue,
Backtrack,
Done,
}
impl<'a, C: Constraint<'a>, P: Fn(&Binding) -> Option<R>, R> Iterator for Query<C, P, R> {
type Item = R;
fn next(&mut self) -> Option<Self::Item> {
loop {
match &self.mode {
Search::NextVariable => {
self.mode = Search::NextValue;
if self.unbound.is_empty() {
if let Some(result) = (self.postprocessing)(&self.binding) {
return Some(result);
}
continue;
}
let mut stale_estimates = VariableSet::new_empty();
while let Some(variable) = self.touched_variables.drain_next_ascending() {
stale_estimates = stale_estimates.union(self.influences[variable]);
}
stale_estimates = stale_estimates.subtract(self.binding.bound);
if !stale_estimates.is_empty() {
while let Some(v) = stale_estimates.drain_next_ascending() {
self.estimates[v] = self
.constraint
.estimate(v, &self.binding)
.expect("unconstrained variable in query");
}
self.unbound.sort_unstable_by_key(|v| {
(
Reverse(
self.estimates[*v]
.checked_ilog2()
.map(|magnitude| magnitude + 1)
.unwrap_or(0),
),
self.influences[*v].count(),
)
});
}
let variable = self.unbound.pop().expect("non-empty unbound");
let estimate = self.estimates[variable];
self.stack.push(variable);
let values = self.values[variable].get_or_insert(Vec::new());
values.clear();
values.reserve_exact(estimate.saturating_sub(values.capacity()));
self.constraint.propose(variable, &self.binding, values);
}
Search::NextValue => {
if let Some(&variable) = self.stack.last() {
if let Some(assignment) = self.values[variable]
.as_mut()
.expect("values should be initialized")
.pop()
{
self.binding.set(variable, &assignment);
self.touched_variables.set(variable);
self.mode = Search::NextVariable;
} else {
self.mode = Search::Backtrack;
}
} else {
self.mode = Search::Done;
return None;
}
}
Search::Backtrack => {
if let Some(variable) = self.stack.pop() {
self.binding.unset(variable);
self.unbound.push(variable);
self.touched_variables.set(variable);
self.mode = Search::NextValue;
} else {
self.mode = Search::Done;
return None;
}
}
Search::Done => {
return None;
}
}
}
}
}
impl<'a, C: Constraint<'a>, P: Fn(&Binding) -> Option<R>, R> fmt::Debug for Query<C, P, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Query")
.field("constraint", &std::any::type_name::<C>())
.field("mode", &self.mode)
.field("binding", &self.binding)
.field("stack", &self.stack)
.field("unbound", &self.unbound)
.finish()
}
}
#[macro_export]
macro_rules! find {
($($tokens:tt)*) => {
{
#[allow(unused_mut, unused_variables)]
let mut ctx = $crate::query::VariableContext::new();
macro_rules! __local_find_context {
() => { &mut ctx }
}
$crate::macros::__find_impl!($crate, ctx, $($tokens)*)
}
};
}
pub use find;
#[macro_export]
macro_rules! exists {
(($($vars:tt)*), $Constraint:expr) => {
$crate::query::find!(($($vars)*), $Constraint).next().is_some()
};
($Constraint:expr) => {
$crate::query::find!((), $Constraint).next().is_some()
};
}
pub use exists;
#[macro_export]
macro_rules! temp {
(($Var:ident), $body:expr) => {{
let $Var = __local_find_context!().next_variable();
$body
}};
(($Var:ident,), $body:expr) => {
$crate::temp!(($Var), $body)
};
(($Var:ident, $($rest:ident),+ $(,)?), $body:expr) => {{
$crate::temp!(
($Var),
$crate::temp!(($($rest),+), $body)
)
}};
}
pub use temp;
#[cfg(test)]
mod tests {
use valueschemas::ShortString;
use crate::ignore;
use crate::prelude::valueschemas::*;
use crate::prelude::*;
use crate::examples::literature;
use fake::faker::lorem::en::Sentence;
use fake::faker::lorem::en::Words;
use fake::faker::name::raw::*;
use fake::locales::*;
use fake::Fake;
use std::collections::HashSet;
use super::*;
pub mod knights {
use crate::prelude::*;
attributes! {
"8143F46E812E88C4544E7094080EC523" as loves: valueschemas::GenId;
"D6E0F2A6E5214E1330565B4D4138E55C" as name: valueschemas::ShortString;
}
}
mod social {
use crate::prelude::*;
attributes! {
"A19EC1D9DD534BA9896223A457A6B9C9" as name: valueschemas::ShortString;
"C21DE0AA5BA3446AB886C9640BA60244" as friend: valueschemas::GenId;
}
}
#[test]
fn and_set() {
let mut books = HashSet::<String>::new();
let mut movies = HashSet::<Value<ShortString>>::new();
books.insert("LOTR".to_string());
books.insert("Dragonrider".to_string());
books.insert("Highlander".to_string());
movies.insert("LOTR".to_value());
movies.insert("Highlander".to_value());
let inter: Vec<_> =
find!((a: Value<ShortString>), and!(books.has(a), movies.has(a))).collect();
assert_eq!(inter.len(), 2);
let cross: Vec<_> =
find!((a: Value<ShortString>, b: Value<ShortString>), and!(books.has(a), movies.has(b))).collect();
assert_eq!(cross.len(), 6);
let one: Vec<_> = find!((a: Value<ShortString>),
and!(books.has(a), a.is(ShortString::value_from("LOTR")))
)
.collect();
assert_eq!(one.len(), 1);
}
#[test]
fn pattern() {
let mut kb = TribleSet::new();
(0..1000).for_each(|_| {
let author = fucid();
let book = fucid();
kb += entity! { &author @
literature::firstname: FirstName(EN).fake::<String>(),
literature::lastname: LastName(EN).fake::<String>(),
};
kb += entity! { &book @
literature::author: &author,
literature::title: Words(1..3).fake::<Vec<String>>().join(" "),
literature::quote: Sentence(5..25).fake::<String>().to_blob().get_handle()
};
});
let author = fucid();
let book = fucid();
kb += entity! { &author @
literature::firstname: "Frank",
literature::lastname: "Herbert",
};
kb += entity! { &book @
literature::author: &author,
literature::title: "Dune",
literature::quote: "I must not fear. Fear is the \
mind-killer. Fear is the little-death that brings total \
obliteration. I will face my fear. I will permit it to \
pass over me and through me. And when it has gone past I \
will turn the inner eye to see its path. Where the fear \
has gone there will be nothing. Only I will remain.".to_blob().get_handle()
};
(0..100).for_each(|_| {
let author = fucid();
let book = fucid();
kb += entity! { &author @
literature::firstname: "Fake",
literature::lastname: "Herbert",
};
kb += entity! { &book @
literature::author: &author,
literature::title: Words(1..3).fake::<Vec<String>>().join(" "),
literature::quote: Sentence(5..25).fake::<String>().to_blob().get_handle()
};
});
let r: Vec<_> = find!(
(author: Value<_>, book: Value<_>, title: Value<_>, quote: Value<_>),
pattern!(&kb, [
{?author @
literature::firstname: "Frank",
literature::lastname: "Herbert"},
{?book @
literature::author: ?author,
literature::title: ?title,
literature::quote: ?quote
}]))
.collect();
assert_eq!(1, r.len())
}
#[test]
fn constant() {
let r: Vec<_> = find! {
(string: Value<_>, number: Value<_>),
and!(
string.is(ShortString::value_from("Hello World!")),
number.is(I256BE::value_from(42))
)
}
.collect();
assert_eq!(1, r.len())
}
#[test]
fn exists_true() {
assert!(exists!((a: Value<_>), a.is(I256BE::value_from(42))));
}
#[test]
fn exists_false() {
assert!(!exists!(
(a: Value<_>),
and!(a.is(I256BE::value_from(1)), a.is(I256BE::value_from(2)))
));
}
#[test]
fn exists_no_variables_true() {
let mut ctx = VariableContext::new();
let a = ctx.next_variable::<I256BE>();
assert!(exists!(a.is(I256BE::value_from(42))));
}
#[test]
fn find_no_variables_yields_unit() {
let mut ctx = VariableContext::new();
let a = ctx.next_variable::<I256BE>();
let rows: Vec<()> = find!((), a.is(I256BE::value_from(42))).collect();
assert_eq!(rows, vec![()]);
}
#[test]
fn temp_variables_span_patterns() {
use social::*;
let mut kb = TribleSet::new();
let alice = fucid();
let bob = fucid();
kb += entity! { &alice @ name: "Alice", friend: &bob };
kb += entity! { &bob @ name: "Bob" };
let matches: Vec<_> = find!(
(person_name: Value<_>),
temp!((mutual_friend),
and!(
pattern!(&kb, [{ _?person @ name: ?person_name, friend: ?mutual_friend }]),
pattern!(&kb, [{ ?mutual_friend @ name: "Bob" }])
)
)
)
.collect();
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].0.try_from_value::<&str>().unwrap(), "Alice");
}
#[test]
fn ignore_skips_variables() {
let results: Vec<_> = find!(
(x: Value<_>),
ignore!((y), and!(x.is(I256BE::value_from(1)), y.is(I256BE::value_from(2))))
)
.collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].0, I256BE::value_from(1));
}
#[test]
fn estimate_override_debug_order() {
use std::cell::RefCell;
use std::rc::Rc;
let mut ctx = VariableContext::new();
let a = ctx.next_variable::<ShortString>();
let b = ctx.next_variable::<ShortString>();
let base = and!(
a.is(ShortString::value_from("A")),
b.is(ShortString::value_from("B"))
);
let mut wrapper = crate::debug::query::EstimateOverrideConstraint::new(base);
wrapper.set_estimate(a.index, 10);
wrapper.set_estimate(b.index, 1);
let record = Rc::new(RefCell::new(Vec::new()));
let debug = crate::debug::query::DebugConstraint::new(wrapper, Rc::clone(&record));
let q: Query<_, _, _> = Query::new(debug, |_| Some(()));
let r: Vec<_> = q.collect();
assert_eq!(1, r.len());
assert_eq!(&*record.borrow(), &[b.index, a.index]);
}
}