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