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 /// Return the set of variables potentially influenced when the passed
286 /// variable is bound or unbound.
287 ///
288 /// By default this includes all variables used by the constraint except the
289 /// queried one when the constraint contains the variable, otherwise the set
290 /// is empty.
291 fn influence(&self, variable: VariableId) -> VariableSet {
292 let mut vars = self.variables();
293 if vars.is_set(variable) {
294 vars.unset(variable);
295 vars
296 } else {
297 VariableSet::new_empty()
298 }
299 }
300}
301
302impl<'a, T: Constraint<'a> + ?Sized> Constraint<'a> for Box<T> {
303 fn variables(&self) -> VariableSet {
304 let inner: &T = self;
305 inner.variables()
306 }
307
308 fn estimate(&self, variable: VariableId, binding: &Binding) -> Option<usize> {
309 let inner: &T = self;
310 inner.estimate(variable, binding)
311 }
312
313 fn propose(&self, variable: VariableId, binding: &Binding, proposals: &mut Vec<RawValue>) {
314 let inner: &T = self;
315 inner.propose(variable, binding, proposals)
316 }
317
318 fn confirm(&self, variable: VariableId, binding: &Binding, proposals: &mut Vec<RawValue>) {
319 let inner: &T = self;
320 inner.confirm(variable, binding, proposals)
321 }
322
323 fn influence(&self, variable: VariableId) -> VariableSet {
324 let inner: &T = self;
325 inner.influence(variable)
326 }
327}
328
329impl<'a, T: Constraint<'a> + ?Sized> Constraint<'static> for std::sync::Arc<T> {
330 fn variables(&self) -> VariableSet {
331 let inner: &T = self;
332 inner.variables()
333 }
334
335 fn estimate(&self, variable: VariableId, binding: &Binding) -> Option<usize> {
336 let inner: &T = self;
337 inner.estimate(variable, binding)
338 }
339
340 fn propose(&self, variable: VariableId, binding: &Binding, proposals: &mut Vec<RawValue>) {
341 let inner: &T = self;
342 inner.propose(variable, binding, proposals)
343 }
344
345 fn confirm(&self, variable: VariableId, binding: &Binding, proposal: &mut Vec<RawValue>) {
346 let inner: &T = self;
347 inner.confirm(variable, binding, proposal)
348 }
349
350 fn influence(&self, variable: VariableId) -> VariableSet {
351 let inner: &T = self;
352 inner.influence(variable)
353 }
354}
355
356/// A query is an iterator over the results of a query.
357/// It takes a constraint and a post-processing function as input,
358/// and returns the results of the query as a stream of values.
359/// The query engine uses a depth-first search to find solutions to the query,
360/// proposing values for the variables and backtracking when it reaches a dead end.
361/// The query engine is designed to be simple and efficient, providing low, consistent,
362/// and predictable latency, skew resistance, and no required (or possible) tuning.
363/// The query engine is designed to be used in combination with the [Constraint] trait,
364/// which provides a simple and flexible way to implement constraints that can be used
365/// to filter the results of a query.
366///
367/// This struct is usually not created directly, but rather through the `find!` macro,
368/// which provides a convenient way to declare variables and concrete types for them.
369/// And which sets up the nessecairy context for higher-level query languages
370/// like the one provided by the [crate::namespace] module.
371pub struct Query<C, P: Fn(&Binding) -> Option<R>, R> {
372 constraint: C,
373 postprocessing: P,
374 mode: Search,
375 binding: Binding,
376 influences: [VariableSet; 128],
377 estimates: [usize; 128],
378 touched_variables: VariableSet,
379 stack: ArrayVec<VariableId, 128>,
380 unbound: ArrayVec<VariableId, 128>,
381 values: ArrayVec<Option<Vec<RawValue>>, 128>,
382}
383
384impl<'a, C: Constraint<'a>, P: Fn(&Binding) -> Option<R>, R> Query<C, P, R> {
385 /// Create a new query.
386 /// The query takes a constraint and a post-processing function as input,
387 /// and returns the results of the query as a stream of values.
388 /// The post-processing function returns `Option<R>`: returning `None`
389 /// skips the current binding and continues the search.
390 ///
391 /// This method is usually not called directly, but rather through the [find!] macro,
392 pub fn new(constraint: C, postprocessing: P) -> Self {
393 let variables = constraint.variables();
394 let influences = std::array::from_fn(|v| {
395 if variables.is_set(v) {
396 constraint.influence(v)
397 } else {
398 VariableSet::new_empty()
399 }
400 });
401 let binding = Binding::default();
402 let estimates = std::array::from_fn(|v| {
403 if variables.is_set(v) {
404 constraint
405 .estimate(v, &binding)
406 .expect("unconstrained variable in query")
407 } else {
408 usize::MAX
409 }
410 });
411 let mut unbound = ArrayVec::from_iter(variables);
412 unbound.sort_unstable_by_key(|v| {
413 (
414 Reverse(
415 estimates[*v]
416 .checked_ilog2()
417 .map(|magnitude| magnitude + 1)
418 .unwrap_or(0),
419 ),
420 influences[*v].count(),
421 )
422 });
423
424 Query {
425 constraint,
426 postprocessing,
427 mode: Search::NextVariable,
428 binding,
429 influences,
430 estimates,
431 touched_variables: VariableSet::new_empty(),
432 stack: ArrayVec::new(),
433 unbound,
434 values: ArrayVec::from([const { None }; 128]),
435 }
436 }
437}
438
439/// The search mode of the query engine.
440/// The query engine uses a depth-first search to find solutions to the query,
441/// proposing values for the variables and backtracking when it reaches a dead end.
442/// The search mode is used to keep track of the current state of the search.
443/// The search mode can be one of the following:
444/// - `NextVariable` - The query engine is looking for the next variable to assign a value to.
445/// - `NextValue` - The query engine is looking for the next value to assign to a variable.
446/// - `Backtrack` - The query engine is backtracking to try a different value for a variable.
447/// - `Done` - The query engine has finished the search and there are no more results.
448#[derive(Copy, Clone, Debug)]
449enum Search {
450 NextVariable,
451 NextValue,
452 Backtrack,
453 Done,
454}
455
456impl<'a, C: Constraint<'a>, P: Fn(&Binding) -> Option<R>, R> Iterator for Query<C, P, R> {
457 type Item = R;
458
459 fn next(&mut self) -> Option<Self::Item> {
460 loop {
461 match &self.mode {
462 Search::NextVariable => {
463 self.mode = Search::NextValue;
464 if self.unbound.is_empty() {
465 if let Some(result) = (self.postprocessing)(&self.binding) {
466 return Some(result);
467 }
468 // Post-processing rejected this binding; continue
469 // searching (mode is already NextValue).
470 continue;
471 }
472
473 let mut stale_estimates = VariableSet::new_empty();
474
475 while let Some(variable) = self.touched_variables.drain_next_ascending() {
476 stale_estimates = stale_estimates.union(self.influences[variable]);
477 }
478
479 // We remove the bound variables from the stale estimates,
480 // as already bound variables cannot be influenced by the unbound ones.
481 stale_estimates = stale_estimates.subtract(self.binding.bound);
482
483 if !stale_estimates.is_empty() {
484 while let Some(v) = stale_estimates.drain_next_ascending() {
485 self.estimates[v] = self
486 .constraint
487 .estimate(v, &self.binding)
488 .expect("unconstrained variable in query");
489 }
490
491 self.unbound.sort_unstable_by_key(|v| {
492 (
493 Reverse(
494 self.estimates[*v]
495 .checked_ilog2()
496 .map(|magnitude| magnitude + 1)
497 .unwrap_or(0),
498 ),
499 self.influences[*v].count(),
500 )
501 });
502 }
503
504 let variable = self.unbound.pop().expect("non-empty unbound");
505 let estimate = self.estimates[variable];
506
507 self.stack.push(variable);
508 let values = self.values[variable].get_or_insert(Vec::new());
509 values.clear();
510 values.reserve_exact(estimate.saturating_sub(values.capacity()));
511 self.constraint.propose(variable, &self.binding, values);
512 }
513 Search::NextValue => {
514 if let Some(&variable) = self.stack.last() {
515 if let Some(assignment) = self.values[variable]
516 .as_mut()
517 .expect("values should be initialized")
518 .pop()
519 {
520 self.binding.set(variable, &assignment);
521 self.touched_variables.set(variable);
522 self.mode = Search::NextVariable;
523 } else {
524 self.mode = Search::Backtrack;
525 }
526 } else {
527 self.mode = Search::Done;
528 return None;
529 }
530 }
531 Search::Backtrack => {
532 if let Some(variable) = self.stack.pop() {
533 self.binding.unset(variable);
534 // Note that we did not update estiamtes for the unbound variables
535 // as we are backtracking, so the estimates are still valid.
536 // Since we choose this variable before, we know that it would
537 // still go last in the unbound list.
538 self.unbound.push(variable);
539
540 // However, we need to update the touched variables,
541 // as we are backtracking and the variable is no longer bound.
542 // We're essentially restoring the estimate of the touched variables
543 // to the state before we bound this variable.
544 self.touched_variables.set(variable);
545 self.mode = Search::NextValue;
546 } else {
547 self.mode = Search::Done;
548 return None;
549 }
550 }
551 Search::Done => {
552 return None;
553 }
554 }
555 }
556 }
557}
558
559impl<'a, C: Constraint<'a>, P: Fn(&Binding) -> Option<R>, R> fmt::Debug for Query<C, P, R> {
560 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
561 f.debug_struct("Query")
562 .field("constraint", &std::any::type_name::<C>())
563 .field("mode", &self.mode)
564 .field("binding", &self.binding)
565 .field("stack", &self.stack)
566 .field("unbound", &self.unbound)
567 .finish()
568 }
569}
570
571/// Iterate over query results, converting each variable via
572/// [`TryFromValue`](crate::value::TryFromValue).
573///
574/// The macro takes two arguments: a tuple of variables with optional type
575/// annotations, and a constraint expression. It injects a `__local_find_context!`
576/// macro that provides the variable context to nested query macros like
577/// [`pattern!`](crate::namespace), [`temp!`], and [`ignore!`].
578///
579/// # Variable syntax
580///
581/// | Syntax | Meaning |
582/// |--------|---------|
583/// | `name` | inferred type, filter on conversion failure |
584/// | `name: Type` | explicit type, filter on conversion failure |
585/// | `name?` | inferred type, yield `Result<T, E>` (no filter) |
586/// | `name: Type?` | explicit type, yield `Result<T, E>` (no filter) |
587///
588/// **Filter semantics (default):** when a variable's conversion fails the
589/// entire row is silently skipped — like a constraint that doesn't match.
590/// For types whose `TryFromValue::Error = Infallible` the error branch is
591/// dead code, so no rows can ever be accidentally filtered.
592///
593/// **`?` pass-through:** appending `?` to a variable makes it yield
594/// `Result<T, E>` directly. Both `Ok` and `Err` values pass through with
595/// no filtering, matching Rust's `?` semantics of "bubble the error to the
596/// caller."
597///
598/// # Examples
599///
600/// ```
601/// # use triblespace_core::prelude::*;
602/// # use triblespace_core::prelude::valueschemas::ShortString;
603/// // Filter semantics — rows where conversion fails are skipped:
604/// let results = find!((x: Value<ShortString>), x.is("foo".to_value())).collect::<Vec<_>>();
605/// ```
606#[macro_export]
607macro_rules! find {
608 ($($tokens:tt)*) => {
609 {
610 let mut ctx = $crate::query::VariableContext::new();
611
612 macro_rules! __local_find_context {
613 () => { &mut ctx }
614 }
615
616 $crate::macros::__find_impl!($crate, ctx, $($tokens)*)
617 }
618 };
619}
620pub use find;
621
622#[macro_export]
623macro_rules! exists {
624 (($($vars:tt)*), $Constraint:expr) => {
625 $crate::query::find!(($($vars)*), $Constraint).next().is_some()
626 };
627}
628pub use exists;
629
630#[macro_export]
631macro_rules! temp {
632 (($Var:ident), $body:expr) => {{
633 let $Var = __local_find_context!().next_variable();
634 $body
635 }};
636 (($Var:ident,), $body:expr) => {
637 $crate::temp!(($Var), $body)
638 };
639 (($Var:ident, $($rest:ident),+ $(,)?), $body:expr) => {{
640 $crate::temp!(
641 ($Var),
642 $crate::temp!(($($rest),+), $body)
643 )
644 }};
645}
646pub use temp;
647
648#[cfg(test)]
649mod tests {
650 use valueschemas::ShortString;
651
652 use crate::ignore;
653 use crate::prelude::valueschemas::*;
654 use crate::prelude::*;
655
656 use crate::examples::literature;
657
658 use fake::faker::lorem::en::Sentence;
659 use fake::faker::lorem::en::Words;
660 use fake::faker::name::raw::*;
661 use fake::locales::*;
662 use fake::Fake;
663
664 use std::collections::HashSet;
665
666 use super::*;
667
668 pub mod knights {
669 use crate::prelude::*;
670
671 attributes! {
672 "8143F46E812E88C4544E7094080EC523" as loves: valueschemas::GenId;
673 "D6E0F2A6E5214E1330565B4D4138E55C" as name: valueschemas::ShortString;
674 }
675 }
676
677 mod social {
678 use crate::prelude::*;
679
680 attributes! {
681 "A19EC1D9DD534BA9896223A457A6B9C9" as name: valueschemas::ShortString;
682 "C21DE0AA5BA3446AB886C9640BA60244" as friend: valueschemas::GenId;
683 }
684 }
685
686 #[test]
687 fn and_set() {
688 let mut books = HashSet::<String>::new();
689 let mut movies = HashSet::<Value<ShortString>>::new();
690
691 books.insert("LOTR".to_string());
692 books.insert("Dragonrider".to_string());
693 books.insert("Highlander".to_string());
694
695 movies.insert("LOTR".to_value());
696 movies.insert("Highlander".to_value());
697
698 let inter: Vec<_> =
699 find!((a: Value<ShortString>), and!(books.has(a), movies.has(a))).collect();
700
701 assert_eq!(inter.len(), 2);
702
703 let cross: Vec<_> =
704 find!((a: Value<ShortString>, b: Value<ShortString>), and!(books.has(a), movies.has(b))).collect();
705
706 assert_eq!(cross.len(), 6);
707
708 let one: Vec<_> = find!((a: Value<ShortString>),
709 and!(books.has(a), a.is(ShortString::value_from("LOTR")))
710 )
711 .collect();
712
713 assert_eq!(one.len(), 1);
714 }
715
716 #[test]
717 fn pattern() {
718 let mut kb = TribleSet::new();
719 (0..1000).for_each(|_| {
720 let author = fucid();
721 let book = fucid();
722 kb += entity! { &author @
723 literature::firstname: FirstName(EN).fake::<String>(),
724 literature::lastname: LastName(EN).fake::<String>(),
725 };
726 kb += entity! { &book @
727 literature::author: &author,
728 literature::title: Words(1..3).fake::<Vec<String>>().join(" "),
729 literature::quote: Sentence(5..25).fake::<String>().to_blob().get_handle()
730 };
731 });
732
733 let author = fucid();
734 let book = fucid();
735 kb += entity! { &author @
736 literature::firstname: "Frank",
737 literature::lastname: "Herbert",
738 };
739 kb += entity! { &book @
740 literature::author: &author,
741 literature::title: "Dune",
742 literature::quote: "I must not fear. Fear is the \
743 mind-killer. Fear is the little-death that brings total \
744 obliteration. I will face my fear. I will permit it to \
745 pass over me and through me. And when it has gone past I \
746 will turn the inner eye to see its path. Where the fear \
747 has gone there will be nothing. Only I will remain.".to_blob().get_handle()
748 };
749
750 (0..100).for_each(|_| {
751 let author = fucid();
752 let book = fucid();
753 kb += entity! { &author @
754 literature::firstname: "Fake",
755 literature::lastname: "Herbert",
756 };
757 kb += entity! { &book @
758 literature::author: &author,
759 literature::title: Words(1..3).fake::<Vec<String>>().join(" "),
760 literature::quote: Sentence(5..25).fake::<String>().to_blob().get_handle()
761 };
762 });
763
764 let r: Vec<_> = find!(
765 (author: Value<_>, book: Value<_>, title: Value<_>, quote: Value<_>),
766 pattern!(&kb, [
767 {?author @
768 literature::firstname: "Frank",
769 literature::lastname: "Herbert"},
770 {?book @
771 literature::author: ?author,
772 literature::title: ?title,
773 literature::quote: ?quote
774 }]))
775 .collect();
776
777 assert_eq!(1, r.len())
778 }
779
780 #[test]
781 fn constant() {
782 let r: Vec<_> = find! {
783 (string: Value<_>, number: Value<_>),
784 and!(
785 string.is(ShortString::value_from("Hello World!")),
786 number.is(I256BE::value_from(42))
787 )
788 }.collect();
789
790 assert_eq!(1, r.len())
791 }
792
793 #[test]
794 fn exists_true() {
795 assert!(exists!((a: Value<_>), a.is(I256BE::value_from(42))));
796 }
797
798 #[test]
799 fn exists_false() {
800 assert!(!exists!(
801 (a: Value<_>),
802 and!(a.is(I256BE::value_from(1)), a.is(I256BE::value_from(2)))
803 ));
804 }
805
806 #[test]
807 fn temp_variables_span_patterns() {
808 use social::*;
809
810 let mut kb = TribleSet::new();
811 let alice = fucid();
812 let bob = fucid();
813
814 kb += entity! { &alice @ name: "Alice", friend: &bob };
815 kb += entity! { &bob @ name: "Bob" };
816
817 let matches: Vec<_> = find!(
818 (person_name: Value<_>),
819 temp!((mutual_friend),
820 and!(
821 pattern!(&kb, [{ _?person @ name: ?person_name, friend: ?mutual_friend }]),
822 pattern!(&kb, [{ ?mutual_friend @ name: "Bob" }])
823 )
824 )
825 )
826 .collect();
827
828 assert_eq!(matches.len(), 1);
829 assert_eq!(matches[0].0.try_from_value::<&str>().unwrap(), "Alice");
830 }
831
832 #[test]
833 fn ignore_skips_variables() {
834 let results: Vec<_> = find!(
835 (x: Value<_>),
836 ignore!((y), and!(x.is(I256BE::value_from(1)), y.is(I256BE::value_from(2))))
837 )
838 .collect();
839
840 assert_eq!(results.len(), 1);
841 assert_eq!(results[0].0, I256BE::value_from(1));
842 }
843
844 #[test]
845 fn estimate_override_debug_order() {
846 use std::cell::RefCell;
847 use std::rc::Rc;
848
849 let mut ctx = VariableContext::new();
850 let a = ctx.next_variable::<ShortString>();
851 let b = ctx.next_variable::<ShortString>();
852
853 let base = and!(
854 a.is(ShortString::value_from("A")),
855 b.is(ShortString::value_from("B"))
856 );
857
858 let mut wrapper = crate::debug::query::EstimateOverrideConstraint::new(base);
859 wrapper.set_estimate(a.index, 10);
860 wrapper.set_estimate(b.index, 1);
861
862 let record = Rc::new(RefCell::new(Vec::new()));
863 let debug = crate::debug::query::DebugConstraint::new(wrapper, Rc::clone(&record));
864
865 let q: Query<_, _, _> = Query::new(debug, |_| Some(()));
866 let r: Vec<_> = q.collect();
867 assert_eq!(1, r.len());
868 assert_eq!(&*record.borrow(), &[b.index, a.index]);
869 }
870}