Skip to main content

triblespace_core/
query.rs

1//! Query facilities for matching tribles by declaring patterns of constraints.
2//! Build queries with the [`find!`](crate::prelude::find) macro which binds variables and
3//! combines constraint expressions:
4//!
5//! ```
6//! # use triblespace_core::prelude::*;
7//! # use triblespace_core::prelude::valueschemas::ShortString;
8//! let results = find!((x: Value<ShortString>), x.is("foo".to_value())).collect::<Vec<_>>();
9//! ```
10//!
11//! Variables are converted via [`TryFromValue`](crate::value::TryFromValue). By default,
12//! conversion failures silently skip the row (filter semantics). Append `?` to a variable
13//! to receive `Result<T, E>` instead, letting the caller handle errors explicitly.
14//!
15//! For a tour of the language see the "Query Language" chapter in the book.
16//! Conceptual background on schemas and join strategy appears in the
17//! "Query Engine" and "Atreides Join" chapters.
18pub mod constantconstraint;
19pub mod hashmapconstraint;
20pub mod hashsetconstraint;
21pub mod ignore;
22pub mod intersectionconstraint;
23pub mod patchconstraint;
24pub mod regularpathconstraint;
25pub mod unionconstraint;
26mod variableset;
27
28use std::cmp::Reverse;
29use std::fmt;
30use std::iter::FromIterator;
31use std::marker::PhantomData;
32
33use arrayvec::ArrayVec;
34use constantconstraint::*;
35pub use ignore::IgnoreConstraint;
36
37use crate::value::schemas::genid::GenId;
38use crate::value::RawValue;
39use crate::value::Value;
40use crate::value::ValueSchema;
41
42pub use regularpathconstraint::PathEngine;
43pub use regularpathconstraint::PathOp;
44pub use regularpathconstraint::RegularPathConstraint;
45pub use regularpathconstraint::ThompsonEngine;
46pub use variableset::VariableSet;
47
48/// Types storing tribles can implement this trait to expose them to queries.
49/// The trait provides a method to create a constraint for a given trible pattern.
50pub trait TriblePattern {
51    /// The type of the constraint created by the pattern method.
52    type PatternConstraint<'a>: Constraint<'a>
53    where
54        Self: 'a;
55
56    /// Create a constraint for a given trible pattern.
57    /// The method takes three variables, one for each part of the trible.
58    /// The schemas of the entities and attributes are always [GenId], while the value
59    /// schema can be any type implementing [ValueSchema] and is specified as a type parameter.
60    ///
61    /// This method is usually not called directly, but rather through typed query language
62    /// macros like [pattern!][crate::namespace].
63    fn pattern<'a, V: ValueSchema>(
64        &'a self,
65        e: Variable<GenId>,
66        a: Variable<GenId>,
67        v: Variable<V>,
68    ) -> Self::PatternConstraint<'a>;
69}
70
71/// Low-level identifier for a variable in a query.
72pub type VariableId = usize;
73
74/// Context for creating variables in a query.
75/// The context keeps track of the next index to assign to a variable.
76/// This allows for the creation of new anonymous variables in higher-level query languages.
77#[derive(Debug)]
78pub struct VariableContext {
79    pub next_index: VariableId,
80}
81
82impl Default for VariableContext {
83    fn default() -> Self {
84        Self::new()
85    }
86}
87
88impl VariableContext {
89    /// Create a new variable context.
90    /// The context starts with an index of 0.
91    pub fn new() -> Self {
92        VariableContext { next_index: 0 }
93    }
94
95    /// Create a new variable.
96    /// The variable is assigned the next available index.
97    ///
98    /// Panics if the number of variables exceeds 128.
99    ///
100    /// This method is usually not called directly, but rather through typed query language
101    /// macros like [find!][crate::query].
102    pub fn next_variable<T: ValueSchema>(&mut self) -> Variable<T> {
103        assert!(
104            self.next_index < 128,
105            "currently queries support at most 128 variables"
106        );
107        let v = Variable::new(self.next_index);
108        self.next_index += 1;
109        v
110    }
111}
112
113/// A placeholder for unknowns in a query.
114/// Within the query engine each variable is identified by an integer,
115/// which can be accessed via the `index` property.
116/// Variables also have an associated type which is used to parse the [Value]s
117/// found by the query engine.
118#[derive(Debug)]
119pub struct Variable<T: ValueSchema> {
120    pub index: VariableId,
121    typed: PhantomData<T>,
122}
123
124impl<T: ValueSchema> Copy for Variable<T> {}
125
126impl<T: ValueSchema> Clone for Variable<T> {
127    fn clone(&self) -> Self {
128        *self
129    }
130}
131
132impl<T: ValueSchema> Variable<T> {
133    pub fn new(index: VariableId) -> Self {
134        Variable {
135            index,
136            typed: PhantomData,
137        }
138    }
139
140    pub fn extract(self, binding: &Binding) -> &Value<T> {
141        let raw = binding.get(self.index).unwrap_or_else(|| {
142            panic!(
143                "query variable (idx {}) was never bound; ensure it appears in a constraint or remove it from the projection",
144                self.index
145            )
146        });
147        Value::as_transmute_raw(raw)
148    }
149}
150
151/// Collections can implement this trait so that they can be used in queries.
152/// The returned constraint will filter the values assigned to the variable
153/// to only those that are contained in the collection.
154pub trait ContainsConstraint<'a, T: ValueSchema> {
155    type Constraint: Constraint<'a>;
156
157    /// Create a constraint that filters the values assigned to the variable
158    /// to only those that are contained in the collection.
159    ///
160    /// The returned constraint will usually perform a conversion between the
161    /// concrete rust type stored in the collection a [Value] of the appropriate schema
162    /// type for the variable.
163    fn has(self, v: Variable<T>) -> Self::Constraint;
164}
165
166impl<T: ValueSchema> Variable<T> {
167    /// Create a constraint so that only a specific value can be assigned to the variable.
168    pub fn is(self, constant: Value<T>) -> ConstantConstraint {
169        ConstantConstraint::new(self, constant)
170    }
171}
172
173/// The binding keeps track of the values assigned to variables in a query.
174/// It maps variables to values - by their index - via a simple array,
175/// and keeps track of which variables are bound.
176/// It is used to store intermediate results and to pass information
177/// between different constraints.
178/// The binding is mutable, as it is modified by the query engine.
179/// It is not thread-safe and should not be shared between threads.
180/// The binding is a simple data structure that is cheap to clone.
181/// It is not intended to be used as a long-term storage for query results.
182#[derive(Clone, Debug)]
183pub struct Binding {
184    pub bound: VariableSet,
185    values: [RawValue; 128],
186}
187
188impl Binding {
189    /// Create a new empty binding.
190    pub fn set(&mut self, variable: VariableId, value: &RawValue) {
191        self.values[variable] = *value;
192        self.bound.set(variable);
193    }
194
195    /// Unset a variable in the binding.
196    /// This is used to backtrack in the query engine.
197    pub fn unset(&mut self, variable: VariableId) {
198        self.bound.unset(variable);
199    }
200
201    /// Check if a variable is bound in the binding.
202    pub fn get(&self, variable: VariableId) -> Option<&RawValue> {
203        if self.bound.is_set(variable) {
204            Some(&self.values[variable])
205        } else {
206            None
207        }
208    }
209}
210
211impl Default for Binding {
212    fn default() -> Self {
213        Self {
214            bound: VariableSet::new_empty(),
215            values: [[0; 32]; 128],
216        }
217    }
218}
219
220/// A constraint is a predicate used to filter the results of a query.
221/// It restricts the values that can be assigned to a variable.
222/// Constraints can be combined using logical operators like `and` and `or`.
223/// This trait provides methods to estimate, propose, and confirm values for a variable:
224/// - `estimate` method estimates the number of values that match the constraint.
225/// - `propose` method suggests values for a variable that match the constraint.
226/// - `confirm` method verifies a value for a variable that matches the constraint.
227/// - `variables` method returns the set of variables used by the constraint.
228///   The trait is generic over the lifetime of an underlying borrowed data structure that the
229///   constraint might use, such as a [std::collections::HashMap] or a [crate::trible::TribleSet].
230///
231/// Note that the constraint does not store any state, but rather operates on the binding
232/// passed to it by the query engine. This allows the query engine to efficiently
233/// backtrack and try different values for the variables, potentially in parallel.
234///
235/// The trait is designed to be simple and flexible, allowing for a wide range of
236/// constraints to be implemented, while still allowing for efficient exploration of the
237/// search space by the query engine.
238///
239/// In contrast to many other query languages, the constraints are not evaluated in a
240/// fixed order, but rather the query engine uses the estimates provided by the constraints
241/// to guide the search. This allows for a more flexible and efficient exploration of the
242/// search space, as the query engine can focus on the most promising parts.
243/// This also also obviates the need for complex query optimization techniques, as the
244/// constraints themselves provide the necessary information to guide the search,
245/// and the query engine can adapt dynamically to the data and the query, providing
246/// skew-resistance and predictable performance. This also removes the need for ordered indexes,
247/// allowing for queries to be executed on unsorted data structures like hashmaps, which
248/// enables easy integration with native Rust data structures and libraries.
249/// This also allows for the query engine to be easily extended with new constraints,
250/// so long as they provide reasonable estimates of the number of values that match the constraint.
251/// See the module documentation for notes on the accuracy of these estimates.
252///
253/// The trait is designed to be used in combination with the [Query] struct, which provides
254/// a simple and efficient way to iterate over the results of a query.
255pub trait Constraint<'a> {
256    /// Return the set of variables used by the constraint.
257    /// This is only called once at the beginning of the query.
258    /// The query engine uses this information to keep track of the variables
259    /// that are used by each constraint.
260    fn variables(&self) -> VariableSet;
261
262    /// Estimate the number of values that match the constraint.
263    /// This is used by the query engine to guide the search.
264    /// The estimate should be as accurate as possible, while being cheap to compute,
265    /// and is not required to be exact or a permissible heuristic.
266    /// The binding passed to the method contains the values assigned to the variables so far.
267    ///
268    /// If the variable is not used by the constraint, the method should return `None`.
269    fn estimate(&self, variable: VariableId, binding: &Binding) -> Option<usize>;
270
271    /// Propose values for a variable that match the constraint.
272    /// This is used by the query engine to explore the search space.
273    /// The method should add values to the `proposals` vector that match the constraint.
274    /// The binding passed to the method contains the values assigned to the variables so far.
275    ///
276    /// If the variable is not used by the constraint, the method should do nothing.
277    fn propose(&self, variable: VariableId, binding: &Binding, proposals: &mut Vec<RawValue>);
278
279    /// Confirm a value for a variable that matches the constraint.
280    /// This is used by the query engine to prune the search space, and confirm that a value satisfies the constraint.
281    /// The method should remove values from the `proposals` vector that do not match the constraint.
282    /// The binding passed to the method contains the values assigned to the variables so far.
283    ///
284    /// If the variable is not used by the constraint, the method should do nothing.
285    fn confirm(&self, variable: VariableId, binding: &Binding, proposals: &mut Vec<RawValue>);
286
287    /// Return the set of variables potentially influenced when the passed
288    /// variable is bound or unbound.
289    ///
290    /// By default this includes all variables used by the constraint except the
291    /// queried one when the constraint contains the variable, otherwise the set
292    /// is empty.
293    fn influence(&self, variable: VariableId) -> VariableSet {
294        let mut vars = self.variables();
295        if vars.is_set(variable) {
296            vars.unset(variable);
297            vars
298        } else {
299            VariableSet::new_empty()
300        }
301    }
302}
303
304impl<'a, T: Constraint<'a> + ?Sized> Constraint<'a> for Box<T> {
305    fn variables(&self) -> VariableSet {
306        let inner: &T = self;
307        inner.variables()
308    }
309
310    fn estimate(&self, variable: VariableId, binding: &Binding) -> Option<usize> {
311        let inner: &T = self;
312        inner.estimate(variable, binding)
313    }
314
315    fn propose(&self, variable: VariableId, binding: &Binding, proposals: &mut Vec<RawValue>) {
316        let inner: &T = self;
317        inner.propose(variable, binding, proposals)
318    }
319
320    fn confirm(&self, variable: VariableId, binding: &Binding, proposals: &mut Vec<RawValue>) {
321        let inner: &T = self;
322        inner.confirm(variable, binding, proposals)
323    }
324
325    fn influence(&self, variable: VariableId) -> VariableSet {
326        let inner: &T = self;
327        inner.influence(variable)
328    }
329}
330
331impl<'a, T: Constraint<'a> + ?Sized> Constraint<'static> for std::sync::Arc<T> {
332    fn variables(&self) -> VariableSet {
333        let inner: &T = self;
334        inner.variables()
335    }
336
337    fn estimate(&self, variable: VariableId, binding: &Binding) -> Option<usize> {
338        let inner: &T = self;
339        inner.estimate(variable, binding)
340    }
341
342    fn propose(&self, variable: VariableId, binding: &Binding, proposals: &mut Vec<RawValue>) {
343        let inner: &T = self;
344        inner.propose(variable, binding, proposals)
345    }
346
347    fn confirm(&self, variable: VariableId, binding: &Binding, proposal: &mut Vec<RawValue>) {
348        let inner: &T = self;
349        inner.confirm(variable, binding, proposal)
350    }
351
352    fn influence(&self, variable: VariableId) -> VariableSet {
353        let inner: &T = self;
354        inner.influence(variable)
355    }
356}
357
358/// A query is an iterator over the results of a query.
359/// It takes a constraint and a post-processing function as input,
360/// and returns the results of the query as a stream of values.
361/// The query engine uses a depth-first search to find solutions to the query,
362/// proposing values for the variables and backtracking when it reaches a dead end.
363/// The query engine is designed to be simple and efficient, providing low, consistent,
364/// and predictable latency, skew resistance, and no required (or possible) tuning.
365/// The query engine is designed to be used in combination with the [Constraint] trait,
366/// which provides a simple and flexible way to implement constraints that can be used
367/// to filter the results of a query.
368///
369/// This struct is usually not created directly, but rather through the `find!` macro,
370/// which provides a convenient way to declare variables and concrete types for them.
371/// And which sets up the nessecairy context for higher-level query languages
372/// like the one provided by the [crate::namespace] module.
373pub struct Query<C, P: Fn(&Binding) -> Option<R>, R> {
374    constraint: C,
375    postprocessing: P,
376    mode: Search,
377    binding: Binding,
378    influences: [VariableSet; 128],
379    estimates: [usize; 128],
380    touched_variables: VariableSet,
381    stack: ArrayVec<VariableId, 128>,
382    unbound: ArrayVec<VariableId, 128>,
383    values: ArrayVec<Option<Vec<RawValue>>, 128>,
384}
385
386impl<'a, C: Constraint<'a>, P: Fn(&Binding) -> Option<R>, R> Query<C, P, R> {
387    /// Create a new query.
388    /// The query takes a constraint and a post-processing function as input,
389    /// and returns the results of the query as a stream of values.
390    /// The post-processing function returns `Option<R>`: returning `None`
391    /// skips the current binding and continues the search.
392    ///
393    /// This method is usually not called directly, but rather through the [find!] macro,
394    pub fn new(constraint: C, postprocessing: P) -> Self {
395        let variables = constraint.variables();
396        let influences = std::array::from_fn(|v| {
397            if variables.is_set(v) {
398                constraint.influence(v)
399            } else {
400                VariableSet::new_empty()
401            }
402        });
403        let binding = Binding::default();
404        let estimates = std::array::from_fn(|v| {
405            if variables.is_set(v) {
406                constraint
407                    .estimate(v, &binding)
408                    .expect("unconstrained variable in query")
409            } else {
410                usize::MAX
411            }
412        });
413        let mut unbound = ArrayVec::from_iter(variables);
414        unbound.sort_unstable_by_key(|v| {
415            (
416                Reverse(
417                    estimates[*v]
418                        .checked_ilog2()
419                        .map(|magnitude| magnitude + 1)
420                        .unwrap_or(0),
421                ),
422                influences[*v].count(),
423            )
424        });
425
426        Query {
427            constraint,
428            postprocessing,
429            mode: Search::NextVariable,
430            binding,
431            influences,
432            estimates,
433            touched_variables: VariableSet::new_empty(),
434            stack: ArrayVec::new(),
435            unbound,
436            values: ArrayVec::from([const { None }; 128]),
437        }
438    }
439}
440
441/// The search mode of the query engine.
442/// The query engine uses a depth-first search to find solutions to the query,
443/// proposing values for the variables and backtracking when it reaches a dead end.
444/// The search mode is used to keep track of the current state of the search.
445/// The search mode can be one of the following:
446/// - `NextVariable` - The query engine is looking for the next variable to assign a value to.
447/// - `NextValue` - The query engine is looking for the next value to assign to a variable.
448/// - `Backtrack` - The query engine is backtracking to try a different value for a variable.
449/// - `Done` - The query engine has finished the search and there are no more results.
450#[derive(Copy, Clone, Debug)]
451enum Search {
452    NextVariable,
453    NextValue,
454    Backtrack,
455    Done,
456}
457
458impl<'a, C: Constraint<'a>, P: Fn(&Binding) -> Option<R>, R> Iterator for Query<C, P, R> {
459    type Item = R;
460
461    fn next(&mut self) -> Option<Self::Item> {
462        loop {
463            match &self.mode {
464                Search::NextVariable => {
465                    self.mode = Search::NextValue;
466                    if self.unbound.is_empty() {
467                        if let Some(result) = (self.postprocessing)(&self.binding) {
468                            return Some(result);
469                        }
470                        // Post-processing rejected this binding; continue
471                        // searching (mode is already NextValue).
472                        continue;
473                    }
474
475                    let mut stale_estimates = VariableSet::new_empty();
476
477                    while let Some(variable) = self.touched_variables.drain_next_ascending() {
478                        stale_estimates = stale_estimates.union(self.influences[variable]);
479                    }
480
481                    // We remove the bound variables from the stale estimates,
482                    // as already bound variables cannot be influenced by the unbound ones.
483                    stale_estimates = stale_estimates.subtract(self.binding.bound);
484
485                    if !stale_estimates.is_empty() {
486                        while let Some(v) = stale_estimates.drain_next_ascending() {
487                            self.estimates[v] = self
488                                .constraint
489                                .estimate(v, &self.binding)
490                                .expect("unconstrained variable in query");
491                        }
492
493                        self.unbound.sort_unstable_by_key(|v| {
494                            (
495                                Reverse(
496                                    self.estimates[*v]
497                                        .checked_ilog2()
498                                        .map(|magnitude| magnitude + 1)
499                                        .unwrap_or(0),
500                                ),
501                                self.influences[*v].count(),
502                            )
503                        });
504                    }
505
506                    let variable = self.unbound.pop().expect("non-empty unbound");
507                    let estimate = self.estimates[variable];
508
509                    self.stack.push(variable);
510                    let values = self.values[variable].get_or_insert(Vec::new());
511                    values.clear();
512                    values.reserve_exact(estimate.saturating_sub(values.capacity()));
513                    self.constraint.propose(variable, &self.binding, values);
514                }
515                Search::NextValue => {
516                    if let Some(&variable) = self.stack.last() {
517                        if let Some(assignment) = self.values[variable]
518                            .as_mut()
519                            .expect("values should be initialized")
520                            .pop()
521                        {
522                            self.binding.set(variable, &assignment);
523                            self.touched_variables.set(variable);
524                            self.mode = Search::NextVariable;
525                        } else {
526                            self.mode = Search::Backtrack;
527                        }
528                    } else {
529                        self.mode = Search::Done;
530                        return None;
531                    }
532                }
533                Search::Backtrack => {
534                    if let Some(variable) = self.stack.pop() {
535                        self.binding.unset(variable);
536                        // Note that we did not update estiamtes for the unbound variables
537                        // as we are backtracking, so the estimates are still valid.
538                        // Since we choose this variable before, we know that it would
539                        // still go last in the unbound list.
540                        self.unbound.push(variable);
541
542                        // However, we need to update the touched variables,
543                        // as we are backtracking and the variable is no longer bound.
544                        // We're essentially restoring the estimate of the touched variables
545                        // to the state before we bound this variable.
546                        self.touched_variables.set(variable);
547                        self.mode = Search::NextValue;
548                    } else {
549                        self.mode = Search::Done;
550                        return None;
551                    }
552                }
553                Search::Done => {
554                    return None;
555                }
556            }
557        }
558    }
559}
560
561impl<'a, C: Constraint<'a>, P: Fn(&Binding) -> Option<R>, R> fmt::Debug for Query<C, P, R> {
562    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
563        f.debug_struct("Query")
564            .field("constraint", &std::any::type_name::<C>())
565            .field("mode", &self.mode)
566            .field("binding", &self.binding)
567            .field("stack", &self.stack)
568            .field("unbound", &self.unbound)
569            .finish()
570    }
571}
572
573/// Iterate over query results, converting each variable via
574/// [`TryFromValue`](crate::value::TryFromValue).
575///
576/// The macro takes two arguments: a tuple of variables with optional type
577/// annotations, and a constraint expression. It injects a `__local_find_context!`
578/// macro that provides the variable context to nested query macros like
579/// [`pattern!`](crate::namespace), [`temp!`], and [`ignore!`].
580///
581/// # Variable syntax
582///
583/// | Syntax | Meaning |
584/// |--------|---------|
585/// | `name` | inferred type, filter on conversion failure |
586/// | `name: Type` | explicit type, filter on conversion failure |
587/// | `name?` | inferred type, yield `Result<T, E>` (no filter) |
588/// | `name: Type?` | explicit type, yield `Result<T, E>` (no filter) |
589///
590/// **Filter semantics (default):** when a variable's conversion fails the
591/// entire row is silently skipped — like a constraint that doesn't match.
592/// For types whose `TryFromValue::Error = Infallible` the error branch is
593/// dead code, so no rows can ever be accidentally filtered.
594///
595/// **`?` pass-through:** appending `?` to a variable makes it yield
596/// `Result<T, E>` directly. Both `Ok` and `Err` values pass through with
597/// no filtering, matching Rust's `?` semantics of "bubble the error to the
598/// caller."
599///
600/// # Examples
601///
602/// ```
603/// # use triblespace_core::prelude::*;
604/// # use triblespace_core::prelude::valueschemas::ShortString;
605/// // Filter semantics — rows where conversion fails are skipped:
606/// let results = find!((x: Value<ShortString>), x.is("foo".to_value())).collect::<Vec<_>>();
607/// ```
608#[macro_export]
609macro_rules! find {
610    // Zero variables: return unit `()` from the closure.
611    ((), $Constraint:expr) => {
612        {
613            let mut ctx = $crate::query::VariableContext::new();
614
615            macro_rules! __local_find_context {
616                () => { &mut ctx }
617            }
618
619            $crate::query::Query::new($Constraint,
620                move |_binding| {
621                    ::core::option::Option::Some(())
622                })
623        }
624    };
625
626    // One or more variables: delegate to the __find_impl proc macro.
627    (($($vars:tt)*), $Constraint:expr) => {
628        {
629            let mut ctx = $crate::query::VariableContext::new();
630
631            macro_rules! __local_find_context {
632                () => { &mut ctx }
633            }
634
635            $crate::macros::__find_impl!($crate, ctx, ($($vars)*), $Constraint)
636        }
637    };
638}
639pub use find;
640
641#[macro_export]
642macro_rules! exists {
643    (($($vars:tt)*), $Constraint:expr) => {
644        $crate::query::find!(($($vars)*), $Constraint).next().is_some()
645    };
646}
647pub use exists;
648
649#[macro_export]
650macro_rules! temp {
651    (($Var:ident), $body:expr) => {{
652        let $Var = __local_find_context!().next_variable();
653        $body
654    }};
655    (($Var:ident,), $body:expr) => {
656        $crate::temp!(($Var), $body)
657    };
658    (($Var:ident, $($rest:ident),+ $(,)?), $body:expr) => {{
659        $crate::temp!(
660            ($Var),
661            $crate::temp!(($($rest),+), $body)
662        )
663    }};
664}
665pub use temp;
666
667#[cfg(test)]
668mod tests {
669    use valueschemas::ShortString;
670
671    use crate::ignore;
672    use crate::prelude::valueschemas::*;
673    use crate::prelude::*;
674
675    use crate::examples::literature;
676
677    use fake::faker::lorem::en::Sentence;
678    use fake::faker::lorem::en::Words;
679    use fake::faker::name::raw::*;
680    use fake::locales::*;
681    use fake::Fake;
682
683    use std::collections::HashSet;
684
685    use super::*;
686
687    pub mod knights {
688        use crate::prelude::*;
689
690        attributes! {
691            "8143F46E812E88C4544E7094080EC523" as loves: valueschemas::GenId;
692            "D6E0F2A6E5214E1330565B4D4138E55C" as name: valueschemas::ShortString;
693        }
694    }
695
696    mod social {
697        use crate::prelude::*;
698
699        attributes! {
700            "A19EC1D9DD534BA9896223A457A6B9C9" as name: valueschemas::ShortString;
701            "C21DE0AA5BA3446AB886C9640BA60244" as friend: valueschemas::GenId;
702        }
703    }
704
705    #[test]
706    fn and_set() {
707        let mut books = HashSet::<String>::new();
708        let mut movies = HashSet::<Value<ShortString>>::new();
709
710        books.insert("LOTR".to_string());
711        books.insert("Dragonrider".to_string());
712        books.insert("Highlander".to_string());
713
714        movies.insert("LOTR".to_value());
715        movies.insert("Highlander".to_value());
716
717        let inter: Vec<_> =
718            find!((a: Value<ShortString>), and!(books.has(a), movies.has(a))).collect();
719
720        assert_eq!(inter.len(), 2);
721
722        let cross: Vec<_> =
723            find!((a: Value<ShortString>, b: Value<ShortString>), and!(books.has(a), movies.has(b))).collect();
724
725        assert_eq!(cross.len(), 6);
726
727        let one: Vec<_> = find!((a: Value<ShortString>),
728            and!(books.has(a), a.is(ShortString::value_from("LOTR")))
729        )
730        .collect();
731
732        assert_eq!(one.len(), 1);
733    }
734
735    #[test]
736    fn pattern() {
737        let mut kb = TribleSet::new();
738        (0..1000).for_each(|_| {
739            let author = fucid();
740            let book = fucid();
741            kb += entity! { &author @
742               literature::firstname: FirstName(EN).fake::<String>(),
743               literature::lastname: LastName(EN).fake::<String>(),
744            };
745            kb += entity! { &book @
746               literature::author: &author,
747               literature::title: Words(1..3).fake::<Vec<String>>().join(" "),
748               literature::quote: Sentence(5..25).fake::<String>().to_blob().get_handle()
749            };
750        });
751
752        let author = fucid();
753        let book = fucid();
754        kb += entity! { &author @
755           literature::firstname: "Frank",
756           literature::lastname: "Herbert",
757        };
758        kb += entity! { &book @
759           literature::author: &author,
760           literature::title: "Dune",
761           literature::quote: "I must not fear. Fear is the \
762                   mind-killer. Fear is the little-death that brings total \
763                   obliteration. I will face my fear. I will permit it to \
764                   pass over me and through me. And when it has gone past I \
765                   will turn the inner eye to see its path. Where the fear \
766                   has gone there will be nothing. Only I will remain.".to_blob().get_handle()
767        };
768
769        (0..100).for_each(|_| {
770            let author = fucid();
771            let book = fucid();
772            kb += entity! { &author @
773               literature::firstname: "Fake",
774               literature::lastname: "Herbert",
775            };
776            kb += entity! { &book @
777               literature::author: &author,
778               literature::title: Words(1..3).fake::<Vec<String>>().join(" "),
779               literature::quote: Sentence(5..25).fake::<String>().to_blob().get_handle()
780            };
781        });
782
783        let r: Vec<_> = find!(
784        (author: Value<_>, book: Value<_>, title: Value<_>, quote: Value<_>),
785        pattern!(&kb, [
786        {?author @
787            literature::firstname: "Frank",
788            literature::lastname: "Herbert"},
789        {?book @
790          literature::author: ?author,
791          literature::title: ?title,
792          literature::quote: ?quote
793        }]))
794        .collect();
795
796        assert_eq!(1, r.len())
797    }
798
799    #[test]
800    fn constant() {
801        let r: Vec<_> = find! {
802            (string: Value<_>, number: Value<_>),
803            and!(
804                string.is(ShortString::value_from("Hello World!")),
805                number.is(I256BE::value_from(42))
806            )
807        }.collect();
808
809        assert_eq!(1, r.len())
810    }
811
812    #[test]
813    fn exists_true() {
814        assert!(exists!((a: Value<_>), a.is(I256BE::value_from(42))));
815    }
816
817    #[test]
818    fn exists_false() {
819        assert!(!exists!(
820            (a: Value<_>),
821            and!(a.is(I256BE::value_from(1)), a.is(I256BE::value_from(2)))
822        ));
823    }
824
825    #[test]
826    fn temp_variables_span_patterns() {
827        use social::*;
828
829        let mut kb = TribleSet::new();
830        let alice = fucid();
831        let bob = fucid();
832
833        kb += entity! { &alice @ name: "Alice", friend: &bob };
834        kb += entity! { &bob @ name: "Bob" };
835
836        let matches: Vec<_> = find!(
837            (person_name: Value<_>),
838            temp!((mutual_friend),
839                and!(
840                    pattern!(&kb, [{ _?person @ name: ?person_name, friend: ?mutual_friend }]),
841                    pattern!(&kb, [{ ?mutual_friend @ name: "Bob" }])
842                )
843            )
844        )
845        .collect();
846
847        assert_eq!(matches.len(), 1);
848        assert_eq!(matches[0].0.try_from_value::<&str>().unwrap(), "Alice");
849    }
850
851    #[test]
852    fn ignore_skips_variables() {
853        let results: Vec<_> = find!(
854            (x: Value<_>),
855            ignore!((y), and!(x.is(I256BE::value_from(1)), y.is(I256BE::value_from(2))))
856        )
857        .collect();
858
859        assert_eq!(results.len(), 1);
860        assert_eq!(results[0].0, I256BE::value_from(1));
861    }
862
863    #[test]
864    fn estimate_override_debug_order() {
865        use std::cell::RefCell;
866        use std::rc::Rc;
867
868        let mut ctx = VariableContext::new();
869        let a = ctx.next_variable::<ShortString>();
870        let b = ctx.next_variable::<ShortString>();
871
872        let base = and!(
873            a.is(ShortString::value_from("A")),
874            b.is(ShortString::value_from("B"))
875        );
876
877        let mut wrapper = crate::debug::query::EstimateOverrideConstraint::new(base);
878        wrapper.set_estimate(a.index, 10);
879        wrapper.set_estimate(b.index, 1);
880
881        let record = Rc::new(RefCell::new(Vec::new()));
882        let debug = crate::debug::query::DebugConstraint::new(wrapper, Rc::clone(&record));
883
884        let q: Query<_, _, _> = Query::new(debug, |_| Some(()));
885        let r: Vec<_> = q.collect();
886        assert_eq!(1, r.len());
887        assert_eq!(&*record.borrow(), &[b.index, a.index]);
888    }
889}