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