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::inlineencodings::ShortString;
8//! let results = find!((x: Inline<ShortString>), x.is("foo".to_inline())).collect::<Vec<_>>();
9//! ```
10//!
11//! Variables are converted via [`TryFromInline`](crate::inline::TryFromInline). 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.
18/// [`ConstantConstraint`] — pins a variable to a single value.
19pub mod constantconstraint;
20/// [`EqualityConstraint`](equalityconstraint::EqualityConstraint) — constrains two variables to have the same value.
21pub mod equalityconstraint;
22/// [`latest`](frontier::latest) — the maximal states of an observation DAG.
23pub mod frontier;
24/// Shared finite continuation for immutable, ordered single-variable sources.
25#[doc(hidden)]
26/// [`KeysConstraint`](hashmapconstraint::KeysConstraint) — constrains a variable to HashMap keys.
27pub mod hashmapconstraint;
28/// [`SetConstraint`](hashsetconstraint::SetConstraint) — constrains a variable to HashSet members.
29pub mod hashsetconstraint;
30/// [`IntersectionConstraint`](intersectionconstraint::IntersectionConstraint) — logical AND.
31pub mod intersectionconstraint;
32/// [`ProposalBuffer`] and [`Candidates`] — candidate storage and bit-packed
33/// liveness for one search level.
34mod liveness;
35/// Exact consecutive or explicit row selections.
36mod ordinal;
37/// [`PatchValueConstraint`](patchconstraint::PatchValueConstraint) and [`PatchIdConstraint`](patchconstraint::PatchIdConstraint) — constrains variables to PATCH entries.
38pub mod patchconstraint;
39#[doc(hidden)]
40/// [`InlineRange`](rangeconstraint::InlineRange) — restricts a variable to a byte-lexicographic range.
41pub mod rangeconstraint;
42/// [`resolve`](register::resolve) — registers: states, an order, and the
43/// reader's choice of which are current.
44pub mod register;
45/// [`SortedSliceConstraint`](sortedsliceconstraint::SortedSliceConstraint) — constrains a variable to values in a sorted slice (binary search confirm).
46pub mod sortedsliceconstraint;
47/// [`UnionConstraint`](unionconstraint::UnionConstraint) — logical OR.
48pub mod unionconstraint;
49mod variableset;
50
51use std::cmp::Reverse;
52use std::fmt;
53use std::marker::PhantomData;
54use std::sync::atomic::{AtomicU64, Ordering};
55use std::sync::Arc;
56
57use arrayvec::ArrayVec;
58use constantconstraint::*;
59use ordinal::RowSelection;
60
61use crate::inline::encodings::genid::GenId;
62use crate::inline::Inline;
63use crate::inline::InlineEncoding;
64use crate::inline::RawInline;
65
66/// Re-export of [`VariableSet`](variableset::VariableSet).
67pub use variableset::VariableSet;
68
69/// Re-exports of the candidate/liveness pair. The module they come from is
70/// private on purpose: the bit-packed representation and the region-boundary
71/// invariants that keep it honest are enforced at the boundary of these two
72/// types, so nothing outside `query::liveness` can reach them. Its
73/// module-level comment is the reference for anyone editing it — or writing a
74/// device kernel against [`LIVENESS_WORD_BITS`].
75pub use liveness::{and_words, or_words, Candidates, ProposalBuffer, LIVENESS_WORD_BITS};
76
77impl<T: InlineEncoding> Term<T> {
78    /// Erases the schema type, yielding the runtime representation
79    /// constraint implementations store.
80    pub fn erase(self) -> RawTerm {
81        match self {
82            Term::Var(v) => RawTerm::Var(v.index),
83            Term::Const(c) => RawTerm::Const(c.raw),
84        }
85    }
86}
87
88/// Types storing tribles can implement this trait to expose them to queries.
89/// The trait provides a method to create a constraint for a given trible pattern.
90pub trait TriblePattern {
91    /// The type of the constraint created by the pattern method.
92    ///
93    /// `Send + Sync` is required so the resulting constraint tree can be
94    /// used with the `parallel` feature's rayon iterators. Every in-tree
95    /// pattern backend (TribleSet, SuccinctArchive) satisfies this; custom
96    /// implementations should hold their data behind `Arc` or similar.
97    type PatternConstraint<'a>: Constraint<'a> + Send + Sync
98    where
99        Self: 'a;
100
101    /// Create a constraint for a given trible pattern.
102    /// Each position takes a [`Term`]: either a [`Variable`] to solve for
103    /// or a constant [`Inline`] value baked into the constraint (a constant
104    /// position behaves exactly like a variable the engine has already
105    /// bound, but never appears in the constraint's [`VariableSet`]).
106    /// The schemas of the entities and attributes are always [GenId], while the value
107    /// schema can be any type implementing [InlineEncoding] and is specified as a type parameter.
108    ///
109    /// This method is usually not called directly, but rather through typed query language
110    /// macros like [pattern!][crate::macros::pattern], which pass attribute
111    /// constants and literal values as constant terms.
112    fn pattern<'a, V: InlineEncoding>(
113        &'a self,
114        e: impl Into<Term<GenId>>,
115        a: impl Into<Term<GenId>>,
116        v: impl Into<Term<V>>,
117    ) -> Self::PatternConstraint<'a>;
118}
119
120/// Low-level identifier for a variable in a query.
121pub type VariableId = usize;
122
123/// Context for creating variables in a query.
124/// The context keeps track of the next index to assign to a variable.
125/// This allows for the creation of new anonymous variables in higher-level query languages.
126#[derive(Debug)]
127pub struct VariableContext {
128    /// The index that will be assigned to the next variable.
129    pub next_index: VariableId,
130}
131
132impl Default for VariableContext {
133    fn default() -> Self {
134        Self::new()
135    }
136}
137
138impl VariableContext {
139    /// Create a new variable context.
140    /// The context starts with an index of 0.
141    pub fn new() -> Self {
142        VariableContext { next_index: 0 }
143    }
144
145    /// Create a new variable.
146    /// The variable is assigned the next available index.
147    ///
148    /// Panics if the number of variables exceeds 128.
149    ///
150    /// This method is usually not called directly, but rather through typed query language
151    /// macros like [find!][crate::query].
152    pub fn next_variable<T: InlineEncoding>(&mut self) -> Variable<T> {
153        assert!(
154            self.next_index < 128,
155            "currently queries support at most 128 variables"
156        );
157        let v = Variable::new(self.next_index);
158        self.next_index += 1;
159        v
160    }
161}
162
163/// A placeholder for unknowns in a query.
164/// Within the query engine each variable is identified by an integer,
165/// which can be accessed via the `index` property.
166/// Variables also have an associated type which is used to parse the [Inline]s
167/// found by the query engine.
168#[derive(Debug)]
169pub struct Variable<T: InlineEncoding> {
170    /// The integer index identifying this variable in the [`Binding`].
171    pub index: VariableId,
172    typed: PhantomData<T>,
173}
174
175impl<T: InlineEncoding> Copy for Variable<T> {}
176
177impl<T: InlineEncoding> Clone for Variable<T> {
178    fn clone(&self) -> Self {
179        *self
180    }
181}
182
183impl<T: InlineEncoding> Variable<T> {
184    /// Creates a variable with the given index.
185    pub fn new(index: VariableId) -> Self {
186        Variable {
187            index,
188            typed: PhantomData,
189        }
190    }
191
192    /// Extracts the bound value for this variable from `binding`.
193    ///
194    /// # Panics
195    ///
196    /// Panics if the variable has not been bound.
197    pub fn extract<'b>(self, binding: &Binding<'b>) -> &'b Inline<T> {
198        let raw = binding.get(self.index).unwrap_or_else(|| {
199            panic!(
200                "query variable (idx {}) was never bound before projection. This usually means the variable was projected in `find!` but never appeared in any constraint. If you intended a pure existence query, use `find!((), ...)` or `exists!(constraint)`.",
201                self.index
202            )
203        });
204        Inline::as_transmute_raw(raw)
205    }
206}
207
208/// One position of a triple pattern: either a [`Variable`] the engine
209/// solves for, or a constant [`Inline`] value pinned at construction.
210///
211/// Constants are how the macro layer expresses attribute constants and
212/// literal values without allocating hidden helper variables. A constant
213/// position behaves exactly like a variable that is already bound — the
214/// backends' bound/unbound dispatch handles it with no extra cases — but
215/// it never appears in the constraint's [`VariableSet`]. This keeps the
216/// visible variable set of a `pattern!` equal to the query variables the
217/// user actually wrote, which is what makes
218/// [`or!`](crate::or) over patterns with different attributes or literals
219/// well-formed (all arms declare the same set).
220#[derive(Debug)]
221pub enum Term<T: InlineEncoding> {
222    /// A variable to solve for.
223    Var(Variable<T>),
224    /// A constant value pinned at construction.
225    Const(Inline<T>),
226}
227
228impl<T: InlineEncoding> Copy for Term<T> {}
229
230impl<T: InlineEncoding> Clone for Term<T> {
231    fn clone(&self) -> Self {
232        *self
233    }
234}
235
236impl<T: InlineEncoding> From<Variable<T>> for Term<T> {
237    fn from(v: Variable<T>) -> Self {
238        Term::Var(v)
239    }
240}
241
242impl<T: InlineEncoding> From<Inline<T>> for Term<T> {
243    fn from(value: Inline<T>) -> Self {
244        Term::Const(value)
245    }
246}
247
248/// Untyped runtime form of a [`Term`]: a variable slot index or a pinned
249/// 32-byte value. Constraint implementations store this and use
250/// [`is_var`](RawTerm::is_var) / [`bound`](RawTerm::bound) in place of the
251/// raw `VariableId` comparison and `Binding::get` lookup — a constant term
252/// then flows through the existing bound-position dispatch for free.
253#[derive(Clone, Copy, Debug, Eq, PartialEq)]
254pub enum RawTerm {
255    /// A variable slot index.
256    Var(VariableId),
257    /// A pinned raw value.
258    Const(RawInline),
259}
260
261impl RawTerm {
262    /// Returns `true` when this term is the given variable.
263    #[inline]
264    pub fn is_var(&self, variable: VariableId) -> bool {
265        matches!(self, RawTerm::Var(v) if *v == variable)
266    }
267
268    /// Returns the value this position holds under `binding`: the pinned
269    /// value for a constant, the binding's value (if any) for a variable.
270    ///
271    /// This is the helper that unifies "bound variable" and "constant" —
272    /// backend dispatch keyed on "is this position bound?" treats a
273    /// constant as a position that is born bound, with no extra match arms.
274    #[inline]
275    pub fn position_value<'b>(&'b self, binding: &'b Binding) -> Option<&'b RawInline> {
276        match self {
277            RawTerm::Var(v) => binding.get(*v),
278            RawTerm::Const(c) => Some(c),
279        }
280    }
281
282    /// Adds the term's variable (if it is one) to `set`. Constants stay
283    /// below the variable layer and are never added.
284    #[inline]
285    pub fn add_to(&self, set: &mut VariableSet) {
286        if let RawTerm::Var(v) = self {
287            set.set(*v);
288        }
289    }
290}
291
292/// Collections can implement this trait so that they can be used in queries.
293/// The returned constraint will filter the values assigned to the variable
294/// to only those that are contained in the collection.
295pub trait ContainsConstraint<'a, T: InlineEncoding> {
296    /// The concrete constraint type produced by [`has`](ContainsConstraint::has).
297    type Constraint: Constraint<'a>;
298
299    /// Create a constraint that filters the values assigned to the variable
300    /// to only those that are contained in the collection.
301    ///
302    /// The returned constraint will usually perform a conversion between the
303    /// concrete rust type stored in the collection a [Inline] of the appropriate schema
304    /// type for the variable.
305    fn has(self, v: Variable<T>) -> Self::Constraint;
306}
307
308impl<T: InlineEncoding> Variable<T> {
309    /// Create a constraint so that only a specific value can be assigned to the variable.
310    pub fn is(self, constant: Inline<T>) -> ConstantConstraint {
311        ConstantConstraint::new(self, constant)
312    }
313}
314
315/// The values assigned to the variables of a query — stored as **paths,
316/// not copies**.
317///
318/// A bound variable's value always *originates* from that variable's own
319/// level buffer: [`propose`](Constraint::propose) fills the buffer and
320/// the engine binds by consuming one of its entries. So a binding does
321/// not need to carry the 32 bytes; it carries the `u32` index of the
322/// chosen entry and resolves it through the buffers on read. Two
323/// properties make the index as good as the value:
324///
325/// * A level's buffer is cleared and refilled only when its variable is
326///   (re-)pushed, and the engine only ever pushes a variable that is
327///   currently unbound (deeper levels are unset by backtracking before
328///   their level is re-pushed). While a variable is bound, its buffer is
329///   stable, so its index stays valid for exactly the lifetime of the
330///   binding. Nothing ever appends to a level while its variable is
331///   bound, so the stability is unconditional (see
332///   [`BindingStore::refill`]).
333/// * Buffers are write-once: confirmers kill entries by clearing a
334///   parallel liveness word, and nothing ever moves or rewrites a stored
335///   value once the engine can see it.
336///
337/// `Binding` is therefore a *view* — the index row plus a borrow of the
338/// buffers it indexes into — constructed for the duration of one
339/// constraint call. [`BindingStore`] owns the buffers; the index rows of
340/// a whole batch live in a [`Frontier`].
341///
342/// The payoff is size: an assignment is one `u32` per variable slot
343/// instead of a 32-byte raw value each, a bind is a 4-byte write instead
344/// of a 32-byte copy, cloning the search state no longer memcpies the
345/// values, and a *batch* of bindings is a small integer matrix over
346/// shared buffers rather than a pile of value copies.
347#[derive(Clone, Copy)]
348pub struct Binding<'a> {
349    /// Bitset tracking which variables have been assigned a value.
350    pub bound: VariableSet,
351    indexes: &'a [u32],
352    levels: &'a [LevelValues],
353}
354
355impl fmt::Debug for Binding<'_> {
356    /// Prints the bound variables and the values they resolve to — the
357    /// assignment, not the buffers it is a path through.
358    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359        f.debug_map()
360            .entries(self.bound.into_iter().map(|v| (v, self.get(v))))
361            .finish()
362    }
363}
364
365impl<'a> Binding<'a> {
366    /// Returns the value bound to `variable`, or `None` when it is
367    /// unbound — resolved through `variable`'s level buffer.
368    pub fn get(&self, variable: VariableId) -> Option<&'a RawInline> {
369        if self.bound.is_set(variable) {
370            Some(&self.levels[variable].buffer()[self.indexes[variable] as usize])
371        } else {
372            None
373        }
374    }
375}
376
377/// Backing for the empty [`Binding`]: nothing is bound, so nothing ever
378/// resolves through them, but a view needs something to point at.
379static NO_LEVELS: [LevelValues; 128] = [const { LevelValues::empty() }; 128];
380static NO_INDEXES: [u32; 128] = [0; 128];
381
382impl Default for Binding<'_> {
383    /// The empty binding — no variable is bound.
384    fn default() -> Self {
385        Binding {
386            bound: VariableSet::new_empty(),
387            indexes: &NO_INDEXES,
388            levels: &NO_LEVELS,
389        }
390    }
391}
392
393/// A **batch of parent bindings** — the collection [`Constraint::propose`]
394/// expands and [`Constraint::confirm`] filters against.
395///
396/// Because bindings are indexes rather than values, a frontier is an
397/// *index matrix*, not a pile of copied assignments: one row of `stride`
398/// `u32`s per parent binding, over the level buffers those indexes point
399/// into. Identity selections and their offset slices remain consecutive and
400/// allocation-free, while a genuine permutation borrows explicit row
401/// numbers. Narrowing never copies an index row.
402///
403/// Every row of a frontier has the *same* [`bound`](Frontier::bound) set —
404/// they are all at the same point in the search, differing only in which
405/// values they took. Today's single-binding call sites are a frontier of
406/// one; see [`BindingStore::frontier`].
407#[derive(Clone, Copy)]
408pub struct Frontier<'a> {
409    bound: VariableSet,
410    block: &'a [u32],
411    stride: usize,
412    select: RowSelection<'a>,
413    levels: &'a [LevelValues],
414    /// Whether this frontier is being driven by `QueryParIter`.
415    ///
416    /// Crate-private execution intent lets built-in CPU constraints expose
417    /// work to the same Rayon pool without making ordinary `Iterator`
418    /// queries parallel merely because the crate was built with the
419    /// `parallel` feature.
420    parallel: bool,
421}
422
423impl fmt::Debug for Frontier<'_> {
424    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425        f.debug_list().entries(self.rows()).finish()
426    }
427}
428
429impl Default for Frontier<'_> {
430    /// A batch of exactly one empty binding — the root of every search,
431    /// and the shape a caller with nothing bound hands a constraint.
432    fn default() -> Self {
433        Frontier {
434            bound: VariableSet::new_empty(),
435            block: &NO_INDEXES,
436            stride: NO_INDEXES.len(),
437            select: RowSelection::identity(1),
438            levels: &NO_LEVELS,
439            parallel: false,
440        }
441    }
442}
443
444impl<'a> Frontier<'a> {
445    /// Number of parent bindings in this batch.
446    pub fn len(&self) -> usize {
447        self.select.len()
448    }
449
450    /// True when the batch is empty.
451    pub fn is_empty(&self) -> bool {
452        self.select.len() == 0
453    }
454
455    /// The variables bound in *every* row of this batch.
456    pub fn bound(&self) -> VariableSet {
457        self.bound
458    }
459
460    /// Whether this batch belongs to a query explicitly converted with
461    /// `into_par_iter`.
462    #[cfg(feature = "parallel")]
463    pub(crate) fn parallel(&self) -> bool {
464        self.parallel
465    }
466
467    /// The binding of row `i`.
468    pub fn row(&self, i: usize) -> Binding<'a> {
469        Binding {
470            bound: self.bound,
471            indexes: self.row_indexes(i),
472            levels: self.levels,
473        }
474    }
475
476    /// Iterates the batch's bindings in row order.
477    pub fn rows(&self) -> impl Iterator<Item = Binding<'a>> + '_ {
478        (0..self.len()).map(|i| self.row(i))
479    }
480
481    fn row_indexes(&self, i: usize) -> &'a [u32] {
482        let row = self.select.get(i).expect("frontier row index is in bounds") as usize;
483        &self.block[row * self.stride..(row + 1) * self.stride]
484    }
485
486    /// Translates `positions` (row numbers *in this view*) into row
487    /// numbers of the underlying block, appending them to `out`.
488    ///
489    /// The result is the `select` array of a sub-batch: pair it with
490    /// [`with_select`](Frontier::with_select) to hand a subset of this
491    /// frontier to a child constraint without copying any row.
492    pub fn compose(&self, positions: impl IntoIterator<Item = u32>, out: &mut Vec<u32>) {
493        out.extend(positions.into_iter().map(|position| {
494            self.select
495                .get(position as usize)
496                .expect("frontier composition index is in bounds")
497        }));
498    }
499
500    /// This frontier restricted to `select` — row numbers of the
501    /// underlying block, as produced by [`compose`](Frontier::compose).
502    pub fn with_select<'b>(&self, select: &'b [u32]) -> Frontier<'b>
503    where
504        'a: 'b,
505    {
506        Frontier {
507            bound: self.bound,
508            block: self.block,
509            stride: self.stride,
510            select: RowSelection::explicit(select),
511            levels: self.levels,
512            parallel: self.parallel,
513        }
514    }
515}
516
517/// Owns what a [`Binding`] is a view of: the per-variable level buffers,
518/// plus the index row that picks one entry out of each for callers
519/// outside the search.
520///
521/// The query engine keeps its frontiers' index matrices separately (they
522/// are per search *depth*, while buffers are per *variable*) and reaches
523/// the buffers through [`refill`](BindingStore::refill) and
524/// [`take_chunk`](BindingStore::take_chunk). Code outside the engine
525/// builds a binding over values it picked itself — see
526/// [`bind`](BindingStore::bind) — and turns it into a width-1
527/// [`Frontier`] with [`frontier`](BindingStore::frontier).
528#[derive(Clone)]
529pub struct BindingStore {
530    bound: VariableSet,
531    indexes: [u32; 128],
532    levels: [LevelValues; 128],
533}
534
535impl fmt::Debug for BindingStore {
536    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
537        fmt::Debug::fmt(&self.view(), f)
538    }
539}
540
541impl Default for BindingStore {
542    fn default() -> Self {
543        BindingStore {
544            bound: VariableSet::new_empty(),
545            indexes: [0; 128],
546            levels: std::array::from_fn(|_| LevelValues::default()),
547        }
548    }
549}
550
551impl BindingStore {
552    /// An empty store: nothing bound, every level empty.
553    pub fn new() -> Self {
554        Self::default()
555    }
556
557    /// The current assignment, as [`satisfied`](Constraint::satisfied)
558    /// and result post-processing see it.
559    pub fn view(&self) -> Binding<'_> {
560        Binding {
561            bound: self.bound,
562            indexes: &self.indexes,
563            levels: &self.levels,
564        }
565    }
566
567    /// The current assignment as a **frontier of one** — the batch shape
568    /// [`propose`](Constraint::propose) and [`confirm`](Constraint::confirm)
569    /// take. This is the bridge for every caller that has a single
570    /// binding: a collection of one behaves exactly like the old
571    /// single-binding protocol.
572    pub fn frontier(&self) -> Frontier<'_> {
573        Frontier {
574            bound: self.bound,
575            block: &self.indexes,
576            stride: self.indexes.len(),
577            select: RowSelection::identity(1),
578            levels: &self.levels,
579            parallel: false,
580        }
581    }
582
583    /// Binds `variable` to `value` by appending it to that variable's
584    /// level buffer and pointing the index row at it.
585    ///
586    /// The engine never needs this — its candidates are already in the
587    /// buffer, so it binds by index. This is the entry point for callers
588    /// outside the search (tests, tools) that want a binding over values
589    /// of their own choosing.
590    pub fn bind(&mut self, variable: VariableId, value: &RawInline) {
591        let level = &mut self.levels[variable];
592        let buffer = Arc::make_mut(
593            level
594                .buffer
595                .get_or_insert_with(|| Arc::new(ProposalBuffer::new())),
596        );
597        let index = buffer.len();
598        buffer.push(*value);
599        self.indexes[variable] = index as u32;
600        self.bound.set(variable);
601    }
602
603    /// Unbinds `variable` — the engine's backtracking step.
604    pub fn unset(&mut self, variable: VariableId) {
605        self.bound.unset(variable);
606    }
607
608    /// The set of currently bound variables.
609    pub fn bound(&self) -> VariableSet {
610        self.bound
611    }
612
613    /// Clears `variable`'s level and refills it by proposing over the
614    /// frontier `block`/`select`/`stride` describe.
615    ///
616    /// The level is moved *out* of the array for the duration of the
617    /// call, so `propose` gets `&mut` on it while the remaining levels
618    /// lend immutably through the [`Frontier`] view — the borrow split
619    /// the search needs, with no `unsafe` and no second copy of the
620    /// candidates. This is sound precisely because the engine only ever
621    /// refills the level of a variable it is about to push, which is by
622    /// construction unbound: nothing can be resolving through this level
623    /// while it is away.
624    ///
625    /// This is now the *only* way a level's buffer is written. With the
626    /// resumable-narrowing path gone, no level is ever appended to while
627    /// its variable is bound, so "a bound variable's buffer is stable for
628    /// the lifetime of its binding" holds unconditionally — the
629    /// `debug_assert` below is that invariant.
630    ///
631    /// A uniquely owned buffer is cleared in place so ordinary backtracking
632    /// retains its capacity. If a rayon sibling shares the published buffer,
633    /// refill replaces this branch's handle with a fresh empty buffer before
634    /// calling `propose`; cloning and then clearing the old contents would be
635    /// both slower and semantically unnecessary.
636    pub(crate) fn refill(
637        &mut self,
638        variable: VariableId,
639        block: &[u32],
640        select: RowSelection<'_>,
641        stride: usize,
642        parallel: bool,
643        propose: impl FnOnce(&Frontier<'_>, &mut ProposalBuffer),
644    ) -> usize {
645        debug_assert!(
646            !self.bound.is_set(variable),
647            "refilling a bound variable's level would strand its binding"
648        );
649        let mut level = std::mem::take(&mut self.levels[variable]);
650        let mut buffer = level
651            .buffer
652            .take()
653            .unwrap_or_else(|| Arc::new(ProposalBuffer::new()));
654        if Arc::get_mut(&mut buffer).is_none() {
655            // A rayon sibling still resolves indexes through the published
656            // buffer. Refill discards every old entry, so cloning it before
657            // `clear` would be pure waste: replace our handle with a fresh
658            // empty allocation and leave the sibling's snapshot untouched.
659            buffer = Arc::new(ProposalBuffer::new());
660        }
661        level.pos = 0;
662        let proposed = {
663            let buffer = Arc::get_mut(&mut buffer).expect("sole owner after replacement");
664            buffer.clear();
665            propose(
666                &Frontier {
667                    bound: self.bound,
668                    block,
669                    stride,
670                    select,
671                    levels: &self.levels,
672                    parallel,
673                },
674                buffer,
675            );
676            buffer.len()
677        };
678        level.buffer = Some(buffer);
679        self.levels[variable] = level;
680        proposed
681    }
682
683    /// A [`Frontier`] over the rows `block`/`select`/`stride` describe,
684    /// resolving through this store's level buffers.
685    pub(crate) fn batch<'s>(
686        &'s self,
687        block: &'s [u32],
688        select: RowSelection<'s>,
689        stride: usize,
690        parallel: bool,
691    ) -> Frontier<'s> {
692        Frontier {
693            bound: self.bound,
694            block,
695            stride,
696            select,
697            levels: &self.levels,
698            parallel,
699        }
700    }
701
702    /// How far `variable`'s level cursor has run — the count of entries the
703    /// level has consumed or skipped. With `Level::proposed` this bounds the
704    /// candidates still pending without scanning the liveness words.
705    pub(crate) fn consumed(&self, variable: VariableId) -> usize {
706        self.levels[variable].pos
707    }
708
709    /// Consumes up to `width` further live candidates from `variable`'s
710    /// level and writes the child frontier's rows into `out`, recording
711    /// each child row's parent row number in `parents_out`.
712    ///
713    /// Each consumed candidate `i` carries the parent tag its proposer
714    /// wrote; the child row is that parent's index row with `variable`'s
715    /// slot pointed at `i`. Returns how many rows were produced (zero
716    /// means the level is spent).
717    ///
718    /// This is the fused path: one pass, no intermediate. A descent that
719    /// might be 1:1 uses [`draw`](BindingStore::draw) instead, which defers
720    /// the write until the shape of the draw is known.
721    pub(crate) fn take_chunk(
722        &mut self,
723        variable: VariableId,
724        width: usize,
725        parent_block: &[u32],
726        parent_select: RowSelection<'_>,
727        stride: usize,
728        out: &mut Vec<u32>,
729        parents_out: &mut Vec<u32>,
730    ) -> usize {
731        out.clear();
732        parents_out.clear();
733        let level = &mut self.levels[variable];
734        let buffer = level
735            .buffer
736            .as_deref()
737            .expect("an active level must have a proposal buffer");
738        let pos = &mut level.pos;
739        let mut rows = 0;
740        while rows < width {
741            let Some(i) = buffer.next_live(*pos) else {
742                break;
743            };
744            *pos = i + 1;
745            let parent = parent_select
746                .get(buffer.parent_of(i) as usize)
747                .expect("candidate parent tag is in bounds") as usize;
748            out.extend_from_slice(&parent_block[parent * stride..(parent + 1) * stride]);
749            let base = out.len() - stride;
750            out[base + variable] = i as u32;
751            parents_out.push(parent as u32);
752            rows += 1;
753        }
754        self.bound.set_value(variable, rows != 0);
755        rows
756    }
757
758    /// Consumes up to `width` further live candidates from `variable`'s
759    /// level *without* materialising any child row: each drawn entry's
760    /// index lands in `drawn_out` and the frontier row it was proposed for
761    /// in `parents_out`.
762    ///
763    /// Deferring the write is what lets the engine look at the shape of a
764    /// draw before paying for it — a 1:1 draw needs no new rows at all,
765    /// see [`Query::next_chunk`]. It is not free (two passes instead of
766    /// one), which is exactly why the caller only takes this path when a
767    /// 1:1 draw is *possible*.
768    pub(crate) fn draw(
769        &mut self,
770        variable: VariableId,
771        width: usize,
772        parent_select: RowSelection<'_>,
773        drawn_out: &mut Vec<u32>,
774        parents_out: &mut Vec<u32>,
775    ) {
776        drawn_out.clear();
777        parents_out.clear();
778        let level = &mut self.levels[variable];
779        let buffer = level
780            .buffer
781            .as_deref()
782            .expect("an active level must have a proposal buffer");
783        let pos = &mut level.pos;
784        while drawn_out.len() < width {
785            let Some(i) = buffer.next_live(*pos) else {
786                break;
787            };
788            *pos = i + 1;
789            drawn_out.push(i as u32);
790            parents_out.push(
791                parent_select
792                    .get(buffer.parent_of(i) as usize)
793                    .expect("candidate parent tag is in bounds"),
794            );
795        }
796        self.bound.set_value(variable, !drawn_out.is_empty());
797    }
798
799    /// Whether `variable`'s level has no live candidate left beyond what
800    /// has already been drawn.
801    ///
802    /// Deliberately *not* folded into [`draw`](BindingStore::draw): it is a
803    /// forward scan over the liveness words, so asking after every draw
804    /// would pay a second pass over a level's dead tail that the next draw
805    /// pays anyway. The one caller asks only once the cheap `O(1)`
806    /// conditions for an in-place descent already hold.
807    pub(crate) fn spent(&self, variable: VariableId) -> bool {
808        let level = &self.levels[variable];
809        level.buffer().next_live(level.pos).is_none()
810    }
811}
812
813/// The cooperative protocol that every query participant implements.
814///
815/// A constraint restricts the values that can be assigned to query variables.
816/// The query engine does not plan joins in advance; instead it consults
817/// constraints directly during a depth-first search over partial bindings.
818/// Each constraint reports which variables it touches, estimates how many
819/// candidates remain, enumerates concrete values on demand, and signals
820/// whether its requirements are still satisfiable. This protocol is the
821/// sole interface between the engine and the data — whether that data lives
822/// in a [`TribleSet`](crate::trible::TribleSet), a [`HashMap`](std::collections::HashMap),
823/// or a custom application predicate.
824///
825/// # The protocol
826///
827/// The engine drives the search by calling five methods in a fixed rhythm:
828///
829/// | Method | Role | Called when |
830/// |--------|------|------------|
831/// | [`variables`](Constraint::variables) | Declares which variables the constraint touches. | Once, at query start. |
832/// | [`estimate`](Constraint::estimate) | Predicts the candidate count for a variable. | Before each binding decision. |
833/// | [`propose`](Constraint::propose) | Enumerates candidate values for a variable, for a whole batch of bindings. | On the most selective constraint. |
834/// | [`confirm`](Constraint::confirm) | Filters candidates proposed by another constraint. | On all remaining constraints. |
835/// | [`satisfied`](Constraint::satisfied) | Checks whether fully-bound sub-constraints still hold. | Before propose/confirm in composite constraints. |
836///
837/// [`influence`](Constraint::influence) completes the picture by telling the
838/// engine which estimates to refresh when a variable is bound or unbound.
839///
840/// # Batching
841///
842/// `propose` and `confirm` operate on a **[`Frontier`]** — a collection of
843/// parent bindings — rather than on one binding. Today's single-binding
844/// call sites are a frontier of one ([`BindingStore::frontier`]) and behave
845/// exactly as before; the engine drives wider batches so that every level
846/// of the search offers a source a *wide* region to work on, not just the
847/// root. A source that has nothing to gain from width writes the loop over
848/// [`Frontier::rows`] and is done; one that does (a GPU-resident index, a
849/// SIMD probe) sees the whole batch in one call. Nothing in the protocol
850/// requires a source to seek: iteration and point queries still suffice,
851/// which is what keeps hash maps, PATCHes, succinct archives and
852/// device-resident structures all admissible.
853///
854/// # Statelessness
855///
856/// Constraints are stateless: every method receives the current [`Binding`]
857/// as a parameter rather than maintaining internal bookkeeping. This lets
858/// the engine backtrack freely by unsetting variables in the binding
859/// without notifying the constraints.
860///
861/// # Composability
862///
863/// Constraints combine via [`IntersectionConstraint`](crate::query::intersectionconstraint::IntersectionConstraint)
864/// (logical AND — built by [`and!`](crate::and)) and
865/// [`UnionConstraint`](crate::query::unionconstraint::UnionConstraint)
866/// (logical OR — built by [`or!`](crate::or)). Because every constraint
867/// speaks the same protocol, heterogeneous data sources mix freely in a
868/// single query.
869///
870/// # Implementing a custom constraint
871///
872/// A new constraint only needs to implement [`variables`](Constraint::variables),
873/// [`estimate`](Constraint::estimate), [`propose`](Constraint::propose), and
874/// [`confirm`](Constraint::confirm). Override [`satisfied`](Constraint::satisfied)
875/// when the constraint can detect unsatisfiability before the engine asks
876/// about individual variables (e.g. a fully-bound triple lookup that found
877/// no match). Override [`influence`](Constraint::influence) when binding one
878/// variable changes the estimates for a non-obvious set of others.
879pub trait Constraint<'a> {
880    /// Returns the set of variables this constraint touches.
881    ///
882    /// Called once at query start. The engine uses this to build influence
883    /// graphs and to determine which constraints participate when a
884    /// particular variable is being bound.
885    fn variables(&self) -> VariableSet;
886
887    /// Estimates the number of candidate values for `variable` given the
888    /// current partial `binding`.
889    ///
890    /// Returns `None` when `variable` is not constrained by this constraint.
891    /// The estimate need not be exact — it guides variable ordering, not
892    /// correctness. Tighter estimates lead to better search pruning; see the
893    /// [Atreides join](crate) family for how different estimate fidelities
894    /// affect performance.
895    ///
896    /// Whether the answer is `Some` or `None` must depend only on which
897    /// variables are bound, not on the values they are bound to: every row
898    /// of a [`Frontier`] shares one bound set, and composites read
899    /// relevance off the batch.
900    fn estimate(&self, variable: VariableId, binding: &Binding) -> Option<usize>;
901
902    /// Enumerates candidate values for `variable`, for every row of
903    /// `frontier`, into `proposals`.
904    ///
905    /// Called on the constraint with the lowest estimate for the variable
906    /// being bound. Before appending the candidates of row `r`, the
907    /// proposer calls [`ProposalBuffer::open`] with `r`, which tags them so
908    /// confirmers and the engine can tell whose they are. A proposer may
909    /// visit the rows in any order and interleave them; contiguous segments
910    /// are merely the cheaper shape.
911    ///
912    /// Values are appended to `proposals`; entries appended by an enclosing
913    /// composite's other children may precede them, and must be left
914    /// untouched.
915    ///
916    /// Does nothing when `variable` is not constrained by this constraint.
917    fn propose(
918        &self,
919        variable: VariableId,
920        frontier: &Frontier<'_>,
921        proposals: &mut ProposalBuffer,
922    );
923
924    /// Kills [`Candidates`] entries whose values for `variable` violate
925    /// this constraint under the entry's own frontier row.
926    ///
927    /// Called on every constraint *except* the one that proposed, in order
928    /// of increasing estimate, all killing into the same region —
929    /// sequential kills compute the conjunction. Implementations may only
930    /// kill entries, never revive them, and may skip entries that are
931    /// already dead. Nothing is ever compacted: dead entries stay in place
932    /// and the engine iterates live ones.
933    ///
934    /// The region spans the whole batch: entry `i` belongs to frontier row
935    /// [`cands.parent(i)`](Candidates::parent). A confirmer whose verdict
936    /// depends on the parent binding walks the region with
937    /// [`Candidates::for_each_parent`]; one whose verdict does not simply
938    /// ignores the tags.
939    ///
940    /// Does nothing when `variable` is not constrained by this constraint.
941    fn confirm(&self, variable: VariableId, frontier: &Frontier<'_>, cands: &mut Candidates<'_>);
942
943    /// Returns whether this constraint is consistent with the current
944    /// `binding`.
945    ///
946    /// The default implementation returns `true`. Override this when the
947    /// constraint can cheaply detect that no solution exists — for example,
948    /// a `TribleSetConstraint`
949    /// whose entity, attribute, and value are all bound but the triple is
950    /// absent from the dataset.
951    ///
952    /// Composite constraints propagate this check to their children:
953    /// [`IntersectionConstraint`](crate::query::intersectionconstraint::IntersectionConstraint)
954    /// requires *all* children to be satisfied, while
955    /// [`UnionConstraint`](crate::query::unionconstraint::UnionConstraint)
956    /// requires *at least one*. The union uses this to skip dead variants
957    /// in propose and confirm, preventing values from a satisfied variant
958    /// from leaking through a dead one.
959    fn satisfied(&self, _binding: &Binding) -> bool {
960        true
961    }
962
963    /// Returns the set of variables whose estimates may change when
964    /// `variable` is bound or unbound.
965    ///
966    /// The default includes every variable this constraint touches except
967    /// `variable` itself. Returns an empty set when `variable` is not part
968    /// of this constraint.
969    fn influence(&self, variable: VariableId) -> VariableSet {
970        let mut vars = self.variables();
971        if vars.is_set(variable) {
972            vars.unset(variable);
973            vars
974        } else {
975            VariableSet::new_empty()
976        }
977    }
978}
979
980impl<'a, T: Constraint<'a> + ?Sized> Constraint<'a> for Box<T> {
981    fn variables(&self) -> VariableSet {
982        let inner: &T = self;
983        inner.variables()
984    }
985
986    fn estimate(&self, variable: VariableId, binding: &Binding) -> Option<usize> {
987        let inner: &T = self;
988        inner.estimate(variable, binding)
989    }
990
991    fn propose(
992        &self,
993        variable: VariableId,
994        frontier: &Frontier<'_>,
995        proposals: &mut ProposalBuffer,
996    ) {
997        let inner: &T = self;
998        inner.propose(variable, frontier, proposals)
999    }
1000
1001    fn confirm(&self, variable: VariableId, frontier: &Frontier<'_>, cands: &mut Candidates<'_>) {
1002        let inner: &T = self;
1003        inner.confirm(variable, frontier, cands)
1004    }
1005
1006    fn satisfied(&self, binding: &Binding) -> bool {
1007        let inner: &T = self;
1008        inner.satisfied(binding)
1009    }
1010
1011    fn influence(&self, variable: VariableId) -> VariableSet {
1012        let inner: &T = self;
1013        inner.influence(variable)
1014    }
1015}
1016
1017impl<'a, T: Constraint<'a> + ?Sized> Constraint<'a> for std::sync::Arc<T> {
1018    fn variables(&self) -> VariableSet {
1019        let inner: &T = self;
1020        inner.variables()
1021    }
1022
1023    fn estimate(&self, variable: VariableId, binding: &Binding) -> Option<usize> {
1024        let inner: &T = self;
1025        inner.estimate(variable, binding)
1026    }
1027
1028    fn propose(
1029        &self,
1030        variable: VariableId,
1031        frontier: &Frontier<'_>,
1032        proposals: &mut ProposalBuffer,
1033    ) {
1034        let inner: &T = self;
1035        inner.propose(variable, frontier, proposals)
1036    }
1037
1038    fn confirm(&self, variable: VariableId, frontier: &Frontier<'_>, cands: &mut Candidates<'_>) {
1039        let inner: &T = self;
1040        inner.confirm(variable, frontier, cands)
1041    }
1042
1043    fn satisfied(&self, binding: &Binding) -> bool {
1044        let inner: &T = self;
1045        inner.satisfied(binding)
1046    }
1047
1048    fn influence(&self, variable: VariableId) -> VariableSet {
1049        let inner: &T = self;
1050        inner.influence(variable)
1051    }
1052}
1053
1054/// Per-variable candidate storage for one search level: the proposals for
1055/// the whole batch that produced them, plus how far the engine has consumed
1056/// them. Slots are indexed by [`VariableId`], so siblings — different
1057/// variables chosen at the same depth — never share one; what a slot is
1058/// reused for is the *next* binding of its own variable, whose refill keeps
1059/// the buffer's capacity.
1060#[derive(Clone, Debug)]
1061struct LevelValues {
1062    /// Once proposed and confirmed, a buffer is immutable. Rayon siblings
1063    /// therefore share it and keep only their consumption cursor local.
1064    /// `None` is the never-used state, so an empty [`BindingStore`] needs no
1065    /// heap allocation for its 128 variable slots.
1066    buffer: Option<Arc<ProposalBuffer>>,
1067    /// Consumption position: entries before it are consumed, live entries at
1068    /// or after it are pending. Dead entries are skipped, never moved.
1069    pos: usize,
1070}
1071
1072impl LevelValues {
1073    /// An empty level, const-constructible so the empty [`Binding`] can
1074    /// borrow a `static` array of them.
1075    const fn empty() -> Self {
1076        LevelValues {
1077            buffer: None,
1078            pos: 0,
1079        }
1080    }
1081
1082    /// The published, immutable proposals for this level.
1083    fn buffer(&self) -> &ProposalBuffer {
1084        self.buffer
1085            .as_deref()
1086            .expect("a bound or active level must have a proposal buffer")
1087    }
1088}
1089
1090impl Default for LevelValues {
1091    fn default() -> Self {
1092        Self::empty()
1093    }
1094}
1095
1096/// Widest frontier the engine expands at once.
1097///
1098/// The search keeps a *frontier* — all the rows sitting at one point of the
1099/// tree — and expands up to this many of them in a single
1100/// [`propose`](Constraint::propose)/[`confirm`](Constraint::confirm) pass.
1101/// Width is what makes a level's candidate region large: with a width-1
1102/// frontier only the root level ever proposes widely, and every deeper level
1103/// hands a source a handful of candidates for one parent — measured on real
1104/// data, a median region of 1–7 candidates at every scale, which is far
1105/// below any batch-dispatch threshold.
1106///
1107/// 16384 is chosen to equal
1108/// `triblespace_gpu::batch_confirm::DEFAULT_MIN_CONFIRM_BATCH`, the measured
1109/// crossover at which a device round trip beats the CPU probes. At that
1110/// width a level's region carries at least as many candidates as there are
1111/// rows, so the batched tier is reachable at *every* depth rather than only
1112/// at a wide root.
1113///
1114/// It is a *ceiling*, not the first batch: a level's first chunk is
1115/// [`INITIAL_FRONTIER_WIDTH`], then chunk width grows by
1116/// [`FRONTIER_RAMP_BASE`] until it reaches this ceiling. A conservative tail
1117/// merge may consume the remainder early, but never exceeds the ceiling.
1118///
1119/// The frontier matrices cost `O(width · variables · depth)`, but a level's
1120/// proposal buffer can be larger: proposing `fanout` candidates for every row
1121/// in one group materialises `O(width · fanout)` entries before that level is
1122/// consumed. Each entry currently has 32 bytes of value, a four-byte parent
1123/// tag, and one packed liveness bit — nominally 36.125 bytes, before allocator
1124/// capacity slack. [`FrontierStats::peak_region`] observes this high-water mark
1125/// in entries; [`Query::with_frontier_width`] is the direct way to reduce it
1126/// when a query joins against a high-fanout source.
1127///
1128/// Worst-case optimality is untouched: expanding N prefixes together is the
1129/// same total work as expanding them one at a time, and the AGM bound is a
1130/// statement about output size, not traversal order.
1131///
1132/// Tune per query with [`Query::with_frontier_width`].
1133pub const DEFAULT_FRONTIER_WIDTH: usize = 16384;
1134
1135/// Width of the *first* chunk a level draws. Later chunks grow by
1136/// [`FRONTIER_RAMP_BASE`] up to [`DEFAULT_FRONTIER_WIDTH`] (or whatever
1137/// [`Query::with_frontier_width`] set).
1138///
1139/// Starting at one keeps time-to-first-result identical to plain
1140/// depth-first search. A query the caller stops after one row — `exists!`,
1141/// `.next()` — must not pay to materialise a full-width frontier it will
1142/// throw away, and with a first chunk of one it does exactly the work the
1143/// pre-batching engine did. Measured on a first-row-only join, a flat
1144/// full-width engine is **8.8x** slower than the pre-batching engine;
1145/// one narrow chunk closes the whole gap.
1146///
1147/// This is the same insight as the `INITIAL_CHUNK`/`WIDEN_FACTOR` pair this
1148/// branch deleted, and as the deleted residual engine's rule that search
1149/// width grows geometrically after negative work — recovered at the right
1150/// layer (the frontier) instead of the wrong one (per-parent chunking).
1151///
1152/// Starting at one protects `.next()` and `exists!`; the ramp then trades
1153/// low-demand granularity against full-drain batch width. The base controls
1154/// that trade. Doubling was measured and rejected because its final chunk is
1155/// only about half a drain, but base eight retains seven eighths
1156/// asymptotically while reaching the ceiling in far fewer steps. See
1157/// [`FRONTIER_RAMP_BASE`] for the measured rationale.
1158pub const INITIAL_FRONTIER_WIDTH: usize = 1;
1159
1160/// Factor a level's chunk width grows by after each chunk, from
1161/// [`INITIAL_FRONTIER_WIDTH`] up to the query's ceiling.
1162///
1163/// # Why a base, and why not two
1164///
1165/// A ramp's cost is not its number of steps, it is the size of its LAST
1166/// chunk. Ramping by `b` consumes `1 + b + b^2 + … + b^k`, of which the
1167/// final chunk `b^k` is `(b-1)/b` of the total — so the widest frontier a
1168/// level can build is `N - N/b`, and the peak is what batching exists to
1169/// produce. At `b = 2` that is `N/2`: doubling throws away half the peak,
1170/// which is the whole of the measured 2048 -> 512 that got the geometric
1171/// ramp rejected. The failure was the base, not the ramp.
1172///
1173/// At `b = 8` the peak is `7N/8` and a level reaches a 16384 ceiling in
1174/// `log8(16384) ~ 4.7` chunks rather than doubling's fourteen — the
1175/// amortised expansion overhead stops mattering well before the peak does.
1176///
1177/// A base at or above the ceiling reproduces the one-narrow-chunk-then-
1178/// ceiling schedule this replaced, which is the control arm to measure
1179/// against. Base `1` is NOT that control — `width * 1` never grows, so it
1180/// pins the engine to a width-1 frontier and reproduces the pre-batching
1181/// engine instead. Both are useful arms; they are not the same arm.
1182pub const FRONTIER_RAMP_BASE: usize = 8;
1183
1184/// A query is an iterator over the results of a query.
1185/// It takes a constraint and a post-processing function as input,
1186/// and returns the results of the query as a stream of values.
1187/// The query engine uses a depth-first search to find solutions to the query,
1188/// proposing values for the variables and backtracking when it reaches a dead end.
1189/// The query engine is designed to be simple and efficient, providing low, consistent,
1190/// and predictable latency, skew resistance, and no required (or possible) tuning.
1191/// The query engine is designed to be used in combination with the [Constraint] trait,
1192/// which provides a simple and flexible way to implement constraints that can be used
1193/// to filter the results of a query.
1194///
1195/// This struct is usually not created directly, but rather through the `find!` macro,
1196/// which provides a convenient way to declare variables and concrete types for them.
1197/// And which sets up the nessecairy context for higher-level query languages
1198/// like the one provided by the [`crate::macros`] module.
1199pub struct Query<C, P: Fn(&Binding<'_>) -> Option<R>, R> {
1200    constraint: C,
1201    postprocessing: P,
1202    mode: Search,
1203    bindings: BindingStore,
1204    influences: [VariableSet; 128],
1205    unbound: VariableSet,
1206    /// Index-row width: one slot per variable the query mentions.
1207    slots: usize,
1208    width: usize,
1209    /// Set only by `IntoParallelIterator`; carried into every frontier so a
1210    /// built-in leaf may join the same pool without changing ordinary
1211    /// iterator execution.
1212    parallel: bool,
1213    stack: ArrayVec<Level, 128>,
1214    /// Frontier stack. `depths[0]` is the root (one empty row); `depths[d]`
1215    /// holds the rows with `d` variables bound. Entries above `depth` are
1216    /// retired but keep their allocations for the next descent.
1217    depths: Vec<Depth>,
1218    depth: usize,
1219    /// Scratch for per-row preferred variables, materialized only after the
1220    /// first disagreement in an expansion.
1221    choice: Vec<u32>,
1222    /// Scratch: the level-buffer entry index of each freshly-drawn child row.
1223    drawn: Vec<u32>,
1224    /// Scratch: each freshly-drawn child row's parent row number.
1225    parents: Vec<u32>,
1226    stats: Arc<FrontierStats>,
1227}
1228
1229/// One pushed search level: the variable it binds, how wide its *next*
1230/// chunk may be, and how many candidates its proposer produced.
1231///
1232/// The width is per level rather than per query because the schedule is a
1233/// statement about one level's drain: the first chunk a level hands down is
1234/// a single row, so a caller who stops after one result never pays for a
1235/// batch it will not look at. See [`INITIAL_FRONTIER_WIDTH`].
1236///
1237/// `proposed` is the `O(1)` gate on the in-place descent: a level with more
1238/// candidates than parent rows cannot possibly yield one child per parent,
1239/// so the engine never pays to check. See [`Query::next_chunk`].
1240#[derive(Clone, Copy, Debug)]
1241struct Level {
1242    variable: VariableId,
1243    width: usize,
1244    proposed: usize,
1245}
1246
1247/// Empties one of a [`Depth`]'s `Arc`-shared buffers for rewriting, without
1248/// copying the contents it is about to discard.
1249///
1250/// `Arc::make_mut` deep-clones whenever the `Arc` is shared — which is the
1251/// normal state right after a rayon split, since splitting hands both halves
1252/// the same matrices — and every caller here clears the result on the next
1253/// line. That copy is pure waste, and it is proportional to the frontier:
1254/// `rows * slots` entries, so it grows with exactly the width the engine
1255/// exists to make large.
1256///
1257/// When we are the sole owner the allocation is reused. When we are not, a
1258/// fresh empty buffer replaces our handle and the other half keeps the old
1259/// one untouched, which is the same outcome `make_mut` produces minus the
1260/// memcpy.
1261fn reset_shared<T: Clone>(slot: &mut Arc<Vec<T>>, capacity: usize) -> &mut Vec<T> {
1262    if Arc::get_mut(slot).is_none() {
1263        *slot = Arc::new(Vec::with_capacity(capacity));
1264    }
1265    let buffer = Arc::get_mut(slot).expect("sole owner after replacement");
1266    buffer.clear();
1267    buffer.reserve(capacity);
1268    buffer
1269}
1270
1271/// The variable choice shared by every row, or the first exact partition
1272/// observed when rows disagree.
1273///
1274/// `Unplanned` and `Universal` carry no allocation. A split plan owns the
1275/// stable variable groups and stores an order override only when stable
1276/// grouping genuinely permutes row ordinals.
1277#[derive(Clone, Debug, Default)]
1278enum DepthPlan {
1279    #[default]
1280    Unplanned,
1281    Universal(VariableId),
1282    Split(Arc<SplitPlan>),
1283}
1284
1285/// Explicit metadata for a frontier whose rows choose different variables.
1286#[derive(Debug, Default)]
1287struct SplitPlan {
1288    /// Stable grouped row order. Empty exactly when that order is the
1289    /// consecutive identity `0..rows`.
1290    order_override: Vec<u32>,
1291    /// `(variable, end offset into the grouped order)`, in variable order.
1292    groups: Vec<(VariableId, usize)>,
1293}
1294
1295impl DepthPlan {
1296    /// Row visitation order denoted by this plan.
1297    fn selection(&self, rows: usize) -> RowSelection<'_> {
1298        match self {
1299            Self::Split(plan) if !plan.order_override.is_empty() => {
1300                debug_assert_eq!(plan.order_override.len(), rows);
1301                RowSelection::explicit(&plan.order_override)
1302            }
1303            Self::Unplanned | Self::Universal(_) | Self::Split(_) => RowSelection::identity(rows),
1304        }
1305    }
1306
1307    fn group_count(&self) -> usize {
1308        match self {
1309            Self::Unplanned => 0,
1310            Self::Universal(_) => 1,
1311            Self::Split(plan) => plan.groups.len(),
1312        }
1313    }
1314
1315    fn group(&self, group: usize, rows: usize) -> (VariableId, std::ops::Range<usize>) {
1316        match self {
1317            Self::Unplanned => panic!("an unplanned frontier has no variable groups"),
1318            Self::Universal(variable) => {
1319                assert_eq!(group, 0, "universal plan has exactly one group");
1320                (*variable, 0..rows)
1321            }
1322            Self::Split(plan) => {
1323                let start = if group == 0 {
1324                    0
1325                } else {
1326                    plan.groups[group - 1].1
1327                };
1328                let (variable, end) = plan.groups[group];
1329                (variable, start..end)
1330            }
1331        }
1332    }
1333}
1334
1335/// One frontier: the index matrix of the rows sitting at one point of the
1336/// search, their per-row estimates, and an exact generated-or-split plan.
1337#[derive(Clone, Debug, Default)]
1338struct Depth {
1339    /// Row-major index matrix, `rows * slots` entries.
1340    block: Arc<Vec<u32>>,
1341    rows: usize,
1342    /// Row-major estimate matrix, `rows * slots` entries.
1343    estimates: Arc<Vec<usize>>,
1344    plan: DepthPlan,
1345    /// Next group to expand.
1346    group: usize,
1347    /// Exclusive end of the group range this query owns.
1348    ///
1349    /// Normally this is `plan.group_count()`. A rayon sibling is fenced to one
1350    /// shared-plan group by setting `group_limit = group + 1`; the original
1351    /// advances `group` and retains everything after it. Keeping ownership as
1352    /// a cursor interval lets both halves share a fragmented [`SplitPlan`]
1353    /// without copying or truncating it.
1354    group_limit: usize,
1355    /// Emission cursor, used only when every variable is bound.
1356    emit: usize,
1357}
1358
1359impl Depth {
1360    fn selection(&self) -> RowSelection<'_> {
1361        self.plan.selection(self.rows)
1362    }
1363
1364    fn group(&self, group: usize) -> (VariableId, std::ops::Range<usize>) {
1365        self.plan.group(group, self.rows)
1366    }
1367}
1368
1369/// Observational counters for the batched search — how wide the frontier
1370/// actually got, and how often its rows disagreed about which variable to
1371/// bind next.
1372///
1373/// Fragmentation is the interesting number:
1374/// [`mean_variable_groups`](FrontierStats::mean_variable_groups) is 1.0 when
1375/// every row of every frontier agreed (the batch stayed whole) and rises as
1376/// bindings pull rows onto different adaptive choices. It is measured
1377/// rather than assumed because a row is never moved onto a variable it did
1378/// not choose — see [`Query::plan`].
1379///
1380/// Counters use relaxed atomics and are shared with every rayon clone of
1381/// the query, so a snapshot taken after the iterator is exhausted covers
1382/// the whole (possibly parallel) run.
1383#[derive(Debug, Default)]
1384pub struct FrontierStats {
1385    expansions: AtomicU64,
1386    rows: AtomicU64,
1387    variable_groups: AtomicU64,
1388    proposals: AtomicU64,
1389    peak_region: AtomicU64,
1390    widest: AtomicU64,
1391    inplace_descents: AtomicU64,
1392    copied_descents: AtomicU64,
1393}
1394
1395impl FrontierStats {
1396    /// Number of frontier expansions (one per batch of parent rows).
1397    pub fn expansions(&self) -> u64 {
1398        self.expansions.load(Ordering::Relaxed)
1399    }
1400
1401    /// Total parent rows expanded, summed over expansions.
1402    pub fn rows(&self) -> u64 {
1403        self.rows.load(Ordering::Relaxed)
1404    }
1405
1406    /// Total preferred-variable groups, summed over expansions. Equal to
1407    /// [`expansions`](Self::expansions) when no frontier ever fragmented.
1408    pub fn variable_groups(&self) -> u64 {
1409        self.variable_groups.load(Ordering::Relaxed)
1410    }
1411
1412    /// Total candidates proposed across all levels.
1413    pub fn proposals(&self) -> u64 {
1414        self.proposals.load(Ordering::Relaxed)
1415    }
1416
1417    /// Largest number of proposals materialised by one level at once.
1418    ///
1419    /// A refill proposes for every parent row in one preferred-variable
1420    /// group before the level is consumed in geometrically widening chunks.
1421    /// The resident proposal region can therefore be much larger than the
1422    /// frontier itself. Cumulative [`proposals`](Self::proposals) and row
1423    /// width [`widest`](Self::widest) measure different quantities; neither
1424    /// can be used to recover this proposal-memory high-water mark.
1425    pub fn peak_region(&self) -> u64 {
1426        self.peak_region.load(Ordering::Relaxed)
1427    }
1428
1429    /// Rows in the widest single expansion — the widest frontier the
1430    /// search actually reached.
1431    ///
1432    /// [`mean_width`](Self::mean_width) says what the typical expansion
1433    /// looked like; this says whether the ceiling was ever approached at
1434    /// all. The difference matters when reading a benchmark: a query whose
1435    /// widest frontier is far below `DEFAULT_FRONTIER_WIDTH` cannot
1436    /// demonstrate anything about batch-width thresholds, however the
1437    /// engine behaves — the data simply never filled a batch.
1438    pub fn widest(&self) -> u64 {
1439        self.widest.load(Ordering::Relaxed)
1440    }
1441
1442    /// Descents that reused the parent frontier's matrices in place — the
1443    /// 1:1 case, where the child block *is* the parent block with one more
1444    /// slot filled in and nothing is allocated or copied.
1445    pub fn inplace_descents(&self) -> u64 {
1446        self.inplace_descents.load(Ordering::Relaxed)
1447    }
1448
1449    /// Descents that had to build a fresh child frontier by copying each
1450    /// row of the parent.
1451    pub fn copied_descents(&self) -> u64 {
1452        self.copied_descents.load(Ordering::Relaxed)
1453    }
1454
1455    /// Mean rows per expansion — the width the search actually achieved.
1456    pub fn mean_width(&self) -> f64 {
1457        let expansions = self.expansions();
1458        if expansions == 0 {
1459            0.0
1460        } else {
1461            self.rows() as f64 / expansions as f64
1462        }
1463    }
1464
1465    /// Mean preferred-variable groups per expansion; 1.0 means no frontier
1466    /// ever fragmented.
1467    pub fn mean_variable_groups(&self) -> f64 {
1468        let expansions = self.expansions();
1469        if expansions == 0 {
1470            0.0
1471        } else {
1472            self.variable_groups() as f64 / expansions as f64
1473        }
1474    }
1475}
1476
1477// Manual `Clone` impl, because `#[derive(Clone)]` would require `R: Clone`
1478// which isn't actually needed — `R` only appears in `P`'s return type.
1479#[cfg(feature = "parallel")]
1480impl<C, P, R> Clone for Query<C, P, R>
1481where
1482    C: Clone,
1483    P: Fn(&Binding<'_>) -> Option<R> + Clone,
1484{
1485    fn clone(&self) -> Self {
1486        Self {
1487            constraint: self.constraint.clone(),
1488            postprocessing: self.postprocessing.clone(),
1489            mode: self.mode,
1490            // Consumption cursors are branch-local; immutable published
1491            // proposal buffers are shared by refcount.
1492            bindings: self.bindings.clone(),
1493            influences: self.influences,
1494            unbound: self.unbound,
1495            slots: self.slots,
1496            width: self.width,
1497            parallel: self.parallel,
1498            stack: self.stack.clone(),
1499            // Only the live prefix of the frontier stack matters; the
1500            // retired tail is scratch. Each `Depth`'s matrices sit behind
1501            // `Arc`, so transferring a planned group copies refcounts, not
1502            // megabytes — the copy-on-write lands on whichever owner
1503            // rewrites a frontier first.
1504            depths: self.depths[..=self.depth].to_vec(),
1505            depth: self.depth,
1506            choice: Vec::new(),
1507            drawn: Vec::new(),
1508            parents: Vec::new(),
1509            stats: Arc::clone(&self.stats),
1510        }
1511    }
1512}
1513
1514#[cfg(feature = "parallel")]
1515impl<C, P, R> Query<C, P, R>
1516where
1517    C: Clone,
1518    P: Fn(&Binding<'_>) -> Option<R> + Clone,
1519{
1520    /// Whether retiring the current frontier would expose work still owned
1521    /// by this query.
1522    ///
1523    /// A split is useful only when the left producer has a distinct future
1524    /// after transferring the right producer's frontier unit. That future
1525    /// can be a later group in an ancestor frontier, or candidates from the
1526    /// ancestor's already-proposed level that its geometric cursor has not
1527    /// consumed yet. `proposed > consumed` is intentionally conservative:
1528    /// the unseen suffix may contain only dead candidates, but discovering
1529    /// that is precisely the left continuation's work.
1530    fn has_ancestor_continuation(&self) -> bool {
1531        (0..self.depth).any(|depth| {
1532            let frontier = &self.depths[depth];
1533            frontier.group < frontier.group_limit
1534                || self
1535                    .stack
1536                    .get(depth)
1537                    .is_some_and(|level| level.proposed > self.bindings.consumed(level.variable))
1538        })
1539    }
1540
1541    /// Transfers the next *whole planned group* to a fenced sibling query.
1542    ///
1543    /// A group is all rows of one frontier that chose the same next
1544    /// variable under the same ordered binding history. The sibling owns
1545    /// that group in its entirety; `self` advances past it and retains the
1546    /// continuation — later groups at this depth and, once they are spent,
1547    /// the ancestor source that can draw the next geometric page. No
1548    /// proposal buffer is bisected, so a source's batch remains intact.
1549    ///
1550    /// The sibling is re-rooted at the group's frontier and fenced there:
1551    /// ancestor `Level`s are discarded, while their `LevelValues` remain in
1552    /// the cloned [`BindingStore`] because the group's rows resolve bound
1553    /// indexes through them. Once the owned group is exhausted, the sibling
1554    /// ends instead of replaying an ancestor continuation still owned by
1555    /// `self`.
1556    ///
1557    /// A sole group at the root cannot be transferred: neither half would
1558    /// retain a distinct continuation, and repeated rayon splitting would
1559    /// merely hand the same indivisible work back and forth. Entering it
1560    /// serially may create child groups/pages that *do* have a continuation
1561    /// and can then be transferred safely.
1562    fn split_pending_group(&mut self) -> Option<Self> {
1563        debug_assert_eq!(self.mode, Search::NextGroup);
1564
1565        let depth = self.depth;
1566        let current = self.depths[depth].group;
1567        let group_limit = self.depths[depth].group_limit;
1568        let later_group = current + 1 < group_limit;
1569        if current >= group_limit || !(later_group || self.has_ancestor_continuation()) {
1570            return None;
1571        }
1572
1573        let mut right = self.clone();
1574        self.depths[depth].group += 1;
1575
1576        drop(right.stack.drain(..depth));
1577        drop(right.depths.drain(..depth));
1578        right.depth = 0;
1579
1580        let root = &mut right.depths[0];
1581        root.group_limit = root.group + 1;
1582        Some(right)
1583    }
1584
1585    /// Transfers the un-emitted remainder of a complete frontier page to a
1586    /// fenced sibling, leaving `self` at the parent source continuation.
1587    ///
1588    /// Terminal pages have no `NextGroup` state — planning moves them
1589    /// straight to [`Search::Emit`] — but they are still whole products of a
1590    /// planned group. Treating the page as one indivisible unit is what lets
1591    /// single-variable queries participate in rayon without slicing their
1592    /// proposal buffers or their result frontiers. The sibling preserves the
1593    /// current emission cursor, so the operation is correct even if a future
1594    /// caller exposes splitting after partial consumption.
1595    fn split_terminal_frontier(&mut self) -> Option<Self> {
1596        debug_assert_eq!(self.mode, Search::Emit);
1597        let depth = self.depth;
1598        if depth == 0
1599            || self.depths[depth].emit >= self.depths[depth].rows
1600            || !self.has_ancestor_continuation()
1601        {
1602            return None;
1603        }
1604
1605        let mut right = self.clone();
1606        self.depth -= 1;
1607        self.mode = Search::NextChunk;
1608
1609        drop(right.stack.drain(..depth));
1610        drop(right.depths.drain(..depth));
1611        right.depth = 0;
1612        Some(right)
1613    }
1614}
1615
1616impl<'a, C: Constraint<'a>, P: Fn(&Binding<'_>) -> Option<R>, R> Query<C, P, R> {
1617    /// Create a new query.
1618    /// The query takes a constraint and a post-processing function as input,
1619    /// and returns the results of the query as a stream of values.
1620    /// The post-processing function returns `Option<R>`: returning `None`
1621    /// skips the current binding and continues the search.
1622    ///
1623    /// This method is usually not called directly, but rather through the [find!] macro,
1624    pub fn new(constraint: C, postprocessing: P) -> Self {
1625        let variables = constraint.variables();
1626        let influences = std::array::from_fn(|v| {
1627            if variables.is_set(v) {
1628                constraint.influence(v)
1629            } else {
1630                VariableSet::new_empty()
1631            }
1632        });
1633        let bindings = BindingStore::new();
1634        let slots = variables.find_last_set().map(|v| v + 1).unwrap_or(0);
1635        let estimates: Vec<usize> = (0..slots)
1636            .map(|v| {
1637                if variables.is_set(v) {
1638                    constraint
1639                        .estimate(v, &bindings.view())
1640                        .expect("unconstrained variable in query")
1641                } else {
1642                    usize::MAX
1643                }
1644            })
1645            .collect();
1646
1647        // Constraints whose positions are all constant [`Term`]s (e.g. a
1648        // fully-constant `pattern!` used as an existence check) have an
1649        // empty variable set, so the propose/confirm search never consults
1650        // them. Their truth is binding-independent and `satisfied` is exact
1651        // for them from the start (the fully-bound exactness law: zero
1652        // unbound variables). One check up front settles every such
1653        // subtree; constraints with unbound variables answer an optimistic
1654        // `true` here and are validated by the search as usual.
1655        let mode = if constraint.satisfied(&bindings.view()) {
1656            Search::Plan
1657        } else {
1658            Search::Done
1659        };
1660
1661        let root = Depth {
1662            block: Arc::new(vec![0u32; slots]),
1663            rows: 1,
1664            estimates: Arc::new(estimates),
1665            plan: DepthPlan::Unplanned,
1666            group: 0,
1667            group_limit: 0,
1668            emit: 0,
1669        };
1670
1671        Query {
1672            constraint,
1673            postprocessing,
1674            mode,
1675            bindings,
1676            influences,
1677            unbound: variables,
1678            slots,
1679            width: DEFAULT_FRONTIER_WIDTH,
1680            parallel: false,
1681            stack: ArrayVec::new(),
1682            depths: vec![root],
1683            depth: 0,
1684            choice: Vec::new(),
1685            drawn: Vec::new(),
1686            parents: Vec::new(),
1687            stats: Arc::new(FrontierStats::default()),
1688        }
1689    }
1690
1691    /// Sets the *widest* frontier this query expands at once. See
1692    /// [`DEFAULT_FRONTIER_WIDTH`] for what the number buys and
1693    /// [`INITIAL_FRONTIER_WIDTH`] for the ramp that leads up to it.
1694    ///
1695    /// A width of 1 reduces the engine to expanding one binding at a time —
1696    /// the shape the protocol had before frontiers — which is what the
1697    /// equivalence tests pin. The ramp is a no-op at that ceiling, so the
1698    /// reduction is exact.
1699    ///
1700    /// # Panics
1701    ///
1702    /// Panics if `width` is zero.
1703    pub fn with_frontier_width(mut self, width: usize) -> Self {
1704        assert!(width > 0, "frontier width must be at least one binding");
1705        self.width = width;
1706        self
1707    }
1708
1709    /// Observational counters for this query's frontiers. The handle is
1710    /// shared with every rayon clone, so it stays meaningful under parallel
1711    /// execution.
1712    pub fn stats(&self) -> Arc<FrontierStats> {
1713        Arc::clone(&self.stats)
1714    }
1715
1716    /// The frontier currently at the top of the stack, unrestricted.
1717    fn frontier(&self) -> Frontier<'_> {
1718        let depth = &self.depths[self.depth];
1719        self.bindings
1720            .batch(&depth.block, depth.selection(), self.slots, self.parallel)
1721    }
1722
1723    /// Partitions the top frontier by each row's preferred variable.
1724    ///
1725    /// The preference is the same adaptive, magnitude-bucketed choice the
1726    /// engine has always made — smallest estimate magnitude, most
1727    /// influence — evaluated per row, against that row's own estimates.
1728    /// Rows are never moved onto a variable they did not choose:
1729    /// [`propose`](Constraint::propose) owns candidate support and
1730    /// first-seen order, and the protocol supplies no cross-variable
1731    /// support-equivalence law, so an estimate-compatible variable is not
1732    /// an interchangeable action. All the leeway lives in the magnitude
1733    /// bucketing, which is what makes agreement — and therefore a whole,
1734    /// unsplit batch — the common case.
1735    fn plan(&mut self) {
1736        if self.unbound.is_empty() {
1737            self.depths[self.depth].emit = 0;
1738            self.mode = Search::Emit;
1739            return;
1740        }
1741
1742        let slots = self.slots;
1743        let influences = &self.influences;
1744        let unbound = self.unbound;
1745        let depth = &mut self.depths[self.depth];
1746        let rows = depth.rows;
1747
1748        assert!(rows != 0, "the engine never plans an empty frontier");
1749        u32::try_from(rows - 1).expect("frontier row ordinal exceeds u32");
1750
1751        let preferred = |row: usize| {
1752            let row_estimates = &depth.estimates[row * slots..(row + 1) * slots];
1753            unbound
1754                .into_iter()
1755                .max_by_key(|&v| {
1756                    (
1757                        Reverse(
1758                            row_estimates[v]
1759                                .checked_ilog2()
1760                                .map(|magnitude| magnitude + 1)
1761                                .unwrap_or(0),
1762                        ),
1763                        influences[v].count(),
1764                    )
1765                })
1766                .expect("non-empty unbound")
1767        };
1768
1769        self.choice.clear();
1770        let first = preferred(0);
1771        let disagreement = (1..rows).find_map(|row| {
1772            let variable = preferred(row);
1773            (variable != first).then_some((row, variable))
1774        });
1775
1776        if let Some((row, variable)) = disagreement {
1777            // Only an observed distinction earns storage. Materialise the
1778            // implied unanimous prefix at the first counterexample, then the
1779            // counterexample and remaining exact choices.
1780            self.choice.resize(
1781                row,
1782                u32::try_from(first).expect("query variables fit in u32"),
1783            );
1784            self.choice
1785                .push(u32::try_from(variable).expect("query variables fit in u32"));
1786            for row in row + 1..rows {
1787                self.choice
1788                    .push(u32::try_from(preferred(row)).expect("query variables fit in u32"));
1789            }
1790
1791            let mut counts = [0usize; 128];
1792            for &variable in &self.choice {
1793                counts[variable as usize] += 1;
1794            }
1795            let mut starts = [0usize; 128];
1796            let mut split = SplitPlan::default();
1797            let mut offset = 0;
1798            for variable in 0..slots {
1799                starts[variable] = offset;
1800                if counts[variable] != 0 {
1801                    offset += counts[variable];
1802                    split.groups.push((variable, offset));
1803                }
1804            }
1805            debug_assert_eq!(offset, rows);
1806            debug_assert!(split.groups.len() > 1);
1807
1808            // Stable counting sort is the identity exactly when the input
1809            // choices are already in nondecreasing variable order.
1810            let identity = self.choice.windows(2).all(|pair| pair[0] <= pair[1]);
1811            if !identity {
1812                split.order_override.resize(rows, 0);
1813                for (row, &variable) in self.choice.iter().enumerate() {
1814                    split.order_override[starts[variable as usize]] =
1815                        u32::try_from(row).expect("frontier row ordinal exceeds u32");
1816                    starts[variable as usize] += 1;
1817                }
1818            }
1819            depth.plan = DepthPlan::Split(Arc::new(split));
1820        } else {
1821            depth.plan = DepthPlan::Universal(first);
1822        }
1823        depth.group = 0;
1824        depth.group_limit = depth.plan.group_count();
1825
1826        self.stats.expansions.fetch_add(1, Ordering::Relaxed);
1827        self.stats.rows.fetch_add(rows as u64, Ordering::Relaxed);
1828        self.stats.widest.fetch_max(rows as u64, Ordering::Relaxed);
1829        self.stats.variable_groups.fetch_add(
1830            self.depths[self.depth].group_limit as u64,
1831            Ordering::Relaxed,
1832        );
1833        self.mode = Search::NextGroup;
1834    }
1835
1836    /// Expands the next group of the top frontier: pushes its variable and
1837    /// proposes candidates for every row of the group in one call. When the
1838    /// groups are exhausted the frontier itself is retired.
1839    fn next_group(&mut self) {
1840        let depth = &self.depths[self.depth];
1841        if depth.group >= depth.group_limit {
1842            if self.depth == 0 {
1843                self.mode = Search::Done;
1844            } else {
1845                self.depth -= 1;
1846                self.mode = Search::NextChunk;
1847            }
1848            return;
1849        }
1850        let group = depth.group;
1851        let (variable, range) = depth.group(group);
1852        self.depths[self.depth].group += 1;
1853
1854        self.unbound.unset(variable);
1855
1856        let constraint = &self.constraint;
1857        let depth = &self.depths[self.depth];
1858        let block = Arc::clone(&depth.block);
1859        let selection = depth.selection().slice(range);
1860        let proposed = self.bindings.refill(
1861            variable,
1862            &block,
1863            selection,
1864            self.slots,
1865            self.parallel,
1866            |frontier, proposals| constraint.propose(variable, frontier, proposals),
1867        );
1868        self.stack.push(Level {
1869            variable,
1870            width: INITIAL_FRONTIER_WIDTH.min(self.width),
1871            proposed,
1872        });
1873        self.stats
1874            .proposals
1875            .fetch_add(proposed as u64, Ordering::Relaxed);
1876        self.stats
1877            .peak_region
1878            .fetch_max(proposed as u64, Ordering::Relaxed);
1879        self.mode = Search::NextChunk;
1880    }
1881
1882    /// Consumes the next chunk of the top level's candidates into the child
1883    /// frontier. When the level is spent the group is retired and the next
1884    /// one gets its turn.
1885    ///
1886    /// # In-place 1:1 descent
1887    ///
1888    /// The child frontier is normally built by copying each drawn
1889    /// candidate's parent row and filling in the newly bound variable's
1890    /// slot. When the draw is **1:1** — one surviving child per parent row,
1891    /// in order, covering the whole parent frontier, with nothing left over
1892    /// — no row was gained, lost or reordered, so the child block *is* the
1893    /// parent block with one more slot written. The engine's two standing
1894    /// invariants are what make that safe: confirmers may only kill
1895    /// candidates and never revive them (so a surviving row keeps its
1896    /// identity), and buffers are write-once (so `variable`'s slot in every
1897    /// row was previously unwritten and filling it destroys nothing). The
1898    /// same argument covers the estimate matrix: the rows the child would
1899    /// have copied are bit-identical to the parent's, and the only slots it
1900    /// then overwrites are the ones the influence refresh recomputes
1901    /// anyway.
1902    ///
1903    /// A 1:1 draw that leaves the level spent is also the last thing the
1904    /// parent frontier is ever asked for, so the matrices are handed *down*
1905    /// (swapped with the child's retired allocations) rather than shared —
1906    /// which is what lets a whole chain of 1:1 descents run without a
1907    /// single matrix copy, instead of only the first one.
1908    ///
1909    /// Ownership is not tracked separately: the frontier matrices already
1910    /// sit behind `Arc` so a rayon split copies refcounts, and
1911    /// [`Arc::get_mut`] therefore succeeds exactly when no split or steal
1912    /// is holding the other half. Let the `Arc` be the guard; when it says
1913    /// no, take the copying path.
1914    ///
1915    /// **The fast path is gated so it costs nothing when it cannot fire.**
1916    /// Recognising a 1:1 draw means deferring the child rows until the
1917    /// draw's shape is known, and that deferral is a second pass — measured
1918    /// at +10% on F10 and +20% on F6 when charged to every descent. So the
1919    /// engine asks first, from what it already knows: a level holding
1920    /// `proposed` candidates for `rows` parents can only yield one child
1921    /// per parent if `proposed == rows`. Fan-out levels fail that `O(1)`
1922    /// test and run the fused [`take_chunk`](BindingStore::take_chunk)
1923    /// exactly as before.
1924    fn next_chunk(&mut self) {
1925        let level = *self.stack.last().expect("a level to chunk");
1926        let variable = level.variable;
1927        let parent = self.depth;
1928        let group = self.depths[parent].group - 1;
1929        let (_, range) = self.depths[parent].group(group);
1930        let slots = self.slots;
1931
1932        if self.depths.len() <= parent + 1 {
1933            self.depths.push(Depth::default());
1934        }
1935
1936        // Could this draw possibly be 1:1? One child per parent needs
1937        // exactly as many candidates as there are parent rows, and a chunk
1938        // wide enough to take them all. Both are known before drawing, and
1939        // both are `O(1)`.
1940        let source_rows = range.len();
1941        let speculate = level.proposed == source_rows
1942            && level.width >= source_rows
1943            && self.depths[parent].group >= self.depths[parent].group_limit;
1944
1945        let rows = if speculate {
1946            let selection = self.depths[parent].selection().slice(range.clone());
1947            self.bindings.draw(
1948                variable,
1949                level.width,
1950                selection,
1951                &mut self.drawn,
1952                &mut self.parents,
1953            );
1954            self.drawn.len()
1955        } else {
1956            let (head, tail) = self.depths.split_at_mut(parent + 1);
1957            let selection = head[parent].selection().slice(range);
1958            self.bindings.take_chunk(
1959                variable,
1960                level.width,
1961                &head[parent].block,
1962                selection,
1963                slots,
1964                Arc::make_mut(&mut tail[0].block),
1965                &mut self.parents,
1966            )
1967        };
1968        // Widen the next chunk this level hands down. The first chunk is one
1969        // binding, so a caller who stops after one row pays exactly what
1970        // depth-first paid; after that the width climbs by
1971        // `FRONTIER_RAMP_BASE` to the ceiling, which serves the caller who
1972        // wants a handful of rows without surrendering the peak (see
1973        // `FRONTIER_RAMP_BASE` for why the base is what decides that).
1974        let consumed = self.bindings.consumed(variable);
1975        if let Some(top) = self.stack.last_mut() {
1976            let next = top.width.saturating_mul(FRONTIER_RAMP_BASE).min(self.width);
1977            // Never leave a tail smaller than the chunk that would precede
1978            // it. `proposed - consumed` over-counts (it cannot see which
1979            // entries confirm already killed), so this merges conservatively
1980            // — it can decline a merge it should have made, never force one
1981            // it should not. Both branches are O(1): the level already knows
1982            // how many candidates it proposed and how far the cursor ran.
1983            let remaining = top.proposed.saturating_sub(consumed);
1984            top.width = if remaining < next.saturating_mul(2) {
1985                // `.min(self.width)` is load-bearing, not belt-and-braces:
1986                // `remaining` is bounded by the region, not by the ceiling,
1987                // so merging a tail without it hands down a chunk up to
1988                // twice the width the caller asked for. Measured before the
1989                // cap: 134 of 300 registry spans exceeded a 16384 ceiling,
1990                // worst at 1.93x — and `width` is a ceiling that the
1991                // `O(width · variables · depth)` frontier-memory bound is
1992                // stated against. At the ceiling the merge is a no-op, which
1993                // is right: a level already running flat has no ramp tail.
1994                remaining.max(next).min(self.width)
1995            } else {
1996                next
1997            };
1998        }
1999
2000        if rows == 0 {
2001            self.stack.pop();
2002            self.bindings.unset(variable);
2003            self.unbound.set(variable);
2004            self.mode = Search::NextGroup;
2005            return;
2006        }
2007
2008        // The speculation paid off when every parent really did produce
2009        // exactly one child, in order, and nothing is left pending.
2010        // `spent` is last because it scans the level's remaining liveness
2011        // words.
2012        let reusable = speculate
2013            && rows == source_rows
2014            && self
2015                .parents
2016                .iter()
2017                .enumerate()
2018                .all(|(row, &parent_row)| parent_row as usize == row)
2019            && self.bindings.spent(variable);
2020
2021        let (head, tail) = self.depths.split_at_mut(parent + 1);
2022        let source = &mut head[parent];
2023        let child = &mut tail[0];
2024
2025        let inplace = reusable
2026            && Arc::get_mut(&mut source.block).is_some()
2027            && Arc::get_mut(&mut source.estimates).is_some();
2028
2029        if inplace {
2030            // Hand the matrices down and take the child's retired
2031            // allocations in exchange, so the next 1:1 descent finds a
2032            // uniquely-owned block again and the chain stays copy-free.
2033            std::mem::swap(&mut source.block, &mut child.block);
2034            std::mem::swap(&mut source.estimates, &mut child.estimates);
2035            let block = Arc::get_mut(&mut child.block).expect("unique across the swap");
2036            for (row, &entry) in self.drawn.iter().enumerate() {
2037                block[row * slots + variable] = entry;
2038            }
2039            self.stats.inplace_descents.fetch_add(1, Ordering::Relaxed);
2040        } else {
2041            if speculate {
2042                // Speculated and lost: the rows still have to be written,
2043                // just from the deferred draw rather than fused with it.
2044                let block = reset_shared(&mut child.block, rows * slots);
2045                for (&entry, &parent_row) in self.drawn.iter().zip(self.parents.iter()) {
2046                    let parent_row = parent_row as usize;
2047                    block.extend_from_slice(
2048                        &source.block[parent_row * slots..(parent_row + 1) * slots],
2049                    );
2050                    let base = block.len() - slots;
2051                    block[base + variable] = entry;
2052                }
2053            }
2054            // Inherit each child row's estimates from its parent; the
2055            // refresh below then updates exactly the ones binding
2056            // `variable` can have changed — the same influence-driven
2057            // refresh the single-binding engine did, now once per row of
2058            // the batch. Nothing else can have gone stale: a row's
2059            // estimates were computed against its own binding, so
2060            // backtracking never invalidates them.
2061            let estimates = reset_shared(&mut child.estimates, rows * slots);
2062            for &parent_row in self.parents.iter() {
2063                let parent_row = parent_row as usize;
2064                estimates.extend_from_slice(
2065                    &source.estimates[parent_row * slots..(parent_row + 1) * slots],
2066                );
2067            }
2068            self.stats.copied_descents.fetch_add(1, Ordering::Relaxed);
2069        }
2070        child.rows = rows;
2071        child.plan = DepthPlan::Unplanned;
2072        child.group = 0;
2073        child.group_limit = 0;
2074        child.emit = 0;
2075
2076        let stale = self.influences[variable].intersect(self.unbound);
2077        if !stale.is_empty() {
2078            let child = &mut self.depths[parent + 1];
2079            let block = Arc::clone(&child.block);
2080            let estimates = Arc::make_mut(&mut child.estimates);
2081            let batch =
2082                self.bindings
2083                    .batch(&block, RowSelection::identity(rows), slots, self.parallel);
2084            for row in 0..rows {
2085                let binding = batch.row(row);
2086                for v in stale {
2087                    estimates[row * slots + v] = self
2088                        .constraint
2089                        .estimate(v, &binding)
2090                        .expect("unconstrained variable in query");
2091                }
2092            }
2093        }
2094
2095        self.depth = parent + 1;
2096        self.mode = Search::Plan;
2097    }
2098}
2099
2100/// The search mode of the query engine.
2101///
2102/// The engine is still a depth-first search; what moved is its unit of
2103/// work. A step no longer binds one variable to one value — it expands a
2104/// whole *frontier* of parent bindings at once, so every level's proposal
2105/// region is as wide as the batch rather than as wide as one parent's
2106/// candidate list.
2107///
2108/// - `Plan` — partition the top frontier by each row's preferred variable.
2109/// - `NextGroup` — expand the next group: push its variable and propose for
2110///   all its rows in one call.
2111/// - `NextChunk` — turn the next `width` surviving candidates into the
2112///   child frontier, or retire the group when they run out.
2113/// - `Emit` — every variable is bound; stream the frontier's rows out.
2114/// - `Done` — the search is finished.
2115#[derive(Copy, Clone, Debug, Eq, PartialEq)]
2116enum Search {
2117    Plan,
2118    NextGroup,
2119    NextChunk,
2120    Emit,
2121    Done,
2122}
2123
2124impl<'a, C: Constraint<'a>, P: Fn(&Binding<'_>) -> Option<R>, R> Iterator for Query<C, P, R> {
2125    type Item = R;
2126
2127    fn next(&mut self) -> Option<Self::Item> {
2128        loop {
2129            match self.mode {
2130                Search::Plan => self.plan(),
2131                Search::NextGroup => self.next_group(),
2132                Search::NextChunk => self.next_chunk(),
2133                Search::Emit => {
2134                    // One row per complete binding, in frontier order, no
2135                    // dedup — bag semantics are a property of the rows, not
2136                    // of how many at a time the engine happened to build.
2137                    let frontier = self.frontier();
2138                    let rows = frontier.len();
2139                    let mut emit = self.depths[self.depth].emit;
2140                    let mut result = None;
2141                    while emit < rows {
2142                        let binding = frontier.row(emit);
2143                        emit += 1;
2144                        if let Some(row) = (self.postprocessing)(&binding) {
2145                            result = Some(row);
2146                            break;
2147                        }
2148                    }
2149                    self.depths[self.depth].emit = emit;
2150                    if result.is_some() {
2151                        return result;
2152                    }
2153                    // The batch is spent; hand control back to the level
2154                    // that produced it for its next chunk.
2155                    if self.depth == 0 {
2156                        self.mode = Search::Done;
2157                    } else {
2158                        self.depth -= 1;
2159                        self.mode = Search::NextChunk;
2160                    }
2161                }
2162                Search::Done => return None,
2163            }
2164        }
2165    }
2166}
2167
2168impl<'a, C: Constraint<'a>, P: Fn(&Binding<'_>) -> Option<R>, R> fmt::Debug for Query<C, P, R> {
2169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2170        f.debug_struct("Query")
2171            .field("constraint", &std::any::type_name::<C>())
2172            .field("mode", &self.mode)
2173            .field("width", &self.width)
2174            .field("frontier", &self.frontier())
2175            .field("stack", &self.stack)
2176            .field("unbound", &self.unbound)
2177            .finish()
2178    }
2179}
2180
2181// ---------------------------------------------------------------------------
2182// Parallel execution via rayon.
2183//
2184// `Query` implements `IntoParallelIterator` with `Iter = QueryParIter`.
2185// `QueryParIter` is a separate wrapper type implementing `ParallelIterator`
2186// + `UnindexedProducer`, distinct from `Query` itself to avoid method-name
2187// ambiguity between `Iterator` and `ParallelIterator` — methods like
2188// `.count()`, `.collect()`, `.map()` exist on both.
2189//
2190// Usage: `find!(...).into_par_iter().map(...).collect::<Vec<_>>()`.
2191//
2192// The producer's `split` transfers one *whole planned frontier group* to a
2193// sibling. A group is the rows sharing both an ordered variable-choice prefix
2194// and their preferred next variable. The original producer skips that group
2195// and retains the source continuation, so it can draw the next geometric page
2196// while the sibling evaluates the current one. The sibling is re-rooted and
2197// fenced at the group: it cannot replay later groups or ancestor work.
2198//
2199// No proposal buffer is bisected. This is the load-bearing difference from
2200// candidate sharding: every owned group reaches `propose`/`confirm` at its
2201// natural width, preserving geometric widening and accelerator-sized batches.
2202// A heavy sole root group is honestly serial until executing it produces a
2203// child group with a source continuation; the parallel path does not invent a
2204// destructive intra-group split merely to keep rayon busy.
2205//
2206// A built-in CPU constraint may nevertheless expose *work inside one logical
2207// confirm call* to the same pool. `IntoParallelIterator` marks the query's
2208// frontier views with crate-private execution intent; ordinary `Iterator`
2209// queries do not acquire that intent merely because they happen to run on a
2210// Rayon worker. TribleSet confirmation uses it to divide packed liveness at
2211// word boundaries, where each worker owns disjoint killable words. That leaves
2212// the frontier, proposal buffer, and any accelerator routing whole: it is data
2213// parallelism inside a leaf, not another search-state split.
2214// Every successful split advances the original owner's group/page cursor and
2215// fences that unit out of the sibling's future, so ownership is a strict
2216// progress measure. Rayon's pressure splitter needs no engine-specific split
2217// budget.
2218//
2219// `fold_with` is the terminal leaf: it just drives the existing sequential
2220// `Iterator::next()` and feeds results into the folder. No duplicated
2221// execution logic.
2222// ---------------------------------------------------------------------------
2223
2224#[cfg(feature = "parallel")]
2225pub use parallel::QueryParIter;
2226
2227#[cfg(feature = "parallel")]
2228mod parallel {
2229    use super::*;
2230    use rayon::iter::plumbing::{bridge_unindexed, Folder, UnindexedConsumer, UnindexedProducer};
2231    use rayon::iter::{IntoParallelIterator, ParallelIterator};
2232
2233    /// Parallel iterator over the results of a [`Query`]. Obtained via
2234    /// [`IntoParallelIterator::into_par_iter`] on a `Query`.
2235    ///
2236    /// Drives rayon's work-stealing scheduler through an `UnindexedProducer`
2237    /// impl on the underlying query state. The sequential `Iterator::next`
2238    /// on `Query` is reused as the fold leaf — parallel execution is purely
2239    /// additional, no duplicated engine logic.
2240    ///
2241    /// The inner query is stored in a [`Box`] so rayon's work-stealing
2242    /// `split` (which clones the producer) doesn't memcpy the query's large
2243    /// fixed state on every fork — just a Box pointer copy, with the heap
2244    /// allocation paid only by the child.
2245    pub struct QueryParIter<C, P: Fn(&Binding<'_>) -> Option<R>, R> {
2246        inner: Box<Query<C, P, R>>,
2247    }
2248
2249    impl<'a, C, P, R> IntoParallelIterator for Query<C, P, R>
2250    where
2251        C: Constraint<'a> + Clone + Send + 'a,
2252        P: Fn(&Binding<'_>) -> Option<R> + Clone + Send,
2253        R: Send,
2254    {
2255        type Item = R;
2256        type Iter = QueryParIter<C, P, R>;
2257
2258        fn into_par_iter(mut self) -> Self::Iter {
2259            self.parallel = true;
2260            QueryParIter {
2261                inner: Box::new(self),
2262            }
2263        }
2264    }
2265
2266    impl<'a, C, P, R> UnindexedProducer for QueryParIter<C, P, R>
2267    where
2268        C: Constraint<'a> + Clone + Send + 'a,
2269        P: Fn(&Binding<'_>) -> Option<R> + Clone + Send,
2270        R: Send,
2271    {
2272        type Item = R;
2273
2274        /// Advance the Query's state machine until either a whole planned
2275        /// group can be transferred to a sibling or the sub-query is
2276        /// exhausted / has reached a batch of complete rows (return `None`,
2277        /// leaving `self` as a leaf that `fold_with` will fold sequentially).
2278        fn split(mut self) -> (Self, Option<Self>) {
2279            let q = &mut *self.inner;
2280            loop {
2281                match q.mode {
2282                    Search::Plan => q.plan(),
2283                    Search::NextGroup => {
2284                        if let Some(right) = q.split_pending_group() {
2285                            return (
2286                                self,
2287                                Some(QueryParIter {
2288                                    inner: Box::new(right),
2289                                }),
2290                            );
2291                        }
2292                        q.next_group();
2293                    }
2294                    Search::Emit => {
2295                        if let Some(right) = q.split_terminal_frontier() {
2296                            return (
2297                                self,
2298                                Some(QueryParIter {
2299                                    inner: Box::new(right),
2300                                }),
2301                            );
2302                        }
2303                        return (self, None);
2304                    }
2305                    Search::Done => return (self, None),
2306                    Search::NextChunk => {
2307                        // Draw the next geometric page intact. Planning its
2308                        // child frontier exposes one or more whole groups on
2309                        // the next loop iteration; those are the stealable
2310                        // units, not slices of this proposal buffer.
2311                        q.next_chunk();
2312                    }
2313                }
2314            }
2315        }
2316
2317        fn fold_with<F: Folder<R>>(self, mut folder: F) -> F {
2318            let QueryParIter { inner: mut q } = self;
2319            while !folder.full() {
2320                match q.next() {
2321                    Some(item) => folder = folder.consume(item),
2322                    None => break,
2323                }
2324            }
2325            folder
2326        }
2327    }
2328
2329    impl<'a, C, P, R> ParallelIterator for QueryParIter<C, P, R>
2330    where
2331        C: Constraint<'a> + Clone + Send + 'a,
2332        P: Fn(&Binding<'_>) -> Option<R> + Clone + Send,
2333        R: Send,
2334    {
2335        type Item = R;
2336
2337        fn drive_unindexed<Con>(self, consumer: Con) -> Con::Result
2338        where
2339            Con: UnindexedConsumer<Self::Item>,
2340        {
2341            bridge_unindexed(self, consumer)
2342        }
2343    }
2344}
2345
2346/// Iterate over query results, converting each variable via
2347/// [`TryFromInline`](crate::inline::TryFromInline).
2348///
2349/// The macro takes two arguments: a tuple of variables with optional type
2350/// annotations, and a constraint expression. It injects a `__local_find_context!`
2351/// macro that provides the variable context to nested query macros like
2352/// [`pattern!`](crate::macros::pattern) and [`temp!`](crate::temp).
2353///
2354/// # Variable syntax
2355///
2356/// | Syntax | Meaning |
2357/// |--------|---------|
2358/// | `name` | inferred type, filter on conversion failure |
2359/// | `name: Type` | explicit type, filter on conversion failure |
2360/// | `name?` | inferred type, yield `Result<T, E>` (no filter) |
2361/// | `name: Type?` | explicit type, yield `Result<T, E>` (no filter) |
2362///
2363/// Query heads have BAG semantics: one row per complete satisfying assignment.
2364/// Two assignments that agree on every declared head variable but differ in
2365/// hidden variables therefore produce two rows — hidden-variable multiplicity
2366/// is visible in the output, and the engine performs no deduplication. That is
2367/// deliberate: deduplicating would mean carrying a claim table proportional to
2368/// the distinct result set, sharing it across rayon shards, and deciding what a
2369/// panicking or filtered row does to a claimed key. Dedup is the consumer's
2370/// choice instead: collect into a set, or use two queries (an outer enumeration
2371/// over the values you want distinct, with an inner [`exists!`](crate::exists)
2372/// for the witness). Every projected variable must be unique; repeating a
2373/// variable in the head is a compile error because it would not add a column.
2374///
2375/// The unit form `find!((), constraint)` projects no variables and yields one
2376/// `()` per satisfying assignment — it counts witnesses rather than answering a
2377/// yes/no question. Use [`exists!`](crate::exists) for existence, which stops
2378/// at the first witness.
2379///
2380/// **Filter semantics (default):** when a variable's conversion fails the
2381/// entire row is silently skipped — like a constraint that doesn't match.
2382/// For types whose `TryFromInline::Error = Infallible` the error branch is
2383/// dead code, so no rows can ever be accidentally filtered.
2384///
2385/// **`?` pass-through:** appending `?` to a variable makes it yield
2386/// `Result<T, E>` directly. Both `Ok` and `Err` values pass through with
2387/// no filtering, matching Rust's `?` semantics of "bubble the error to the
2388/// caller."
2389///
2390/// # Examples
2391///
2392/// ```
2393/// # use triblespace_core::prelude::*;
2394/// # use triblespace_core::prelude::inlineencodings::ShortString;
2395/// // Filter semantics — rows where conversion fails are skipped:
2396/// let results = find!((x: Inline<ShortString>), x.is("foo".to_inline())).collect::<Vec<_>>();
2397/// ```
2398#[macro_export]
2399macro_rules! find {
2400    ($($tokens:tt)*) => {
2401        {
2402            #[allow(unused_mut, unused_variables)]
2403            let mut ctx = $crate::query::VariableContext::new();
2404
2405            macro_rules! __local_find_context {
2406                () => { &mut ctx }
2407            }
2408
2409            $crate::macros::__find_impl!($crate, ctx, $($tokens)*)
2410        }
2411    };
2412}
2413/// Re-export of the [`find!`] macro.
2414pub use find;
2415
2416/// Returns `true` when a query produces at least one row.
2417///
2418/// This is equivalent to calling `find!(...).next().is_some()`, but reads more
2419/// directly for existence checks.
2420///
2421/// # Forms
2422///
2423/// - `exists!(constraint)` checks a pure constraint with no projected
2424///   variables.
2425/// - `exists!((vars...), constraint)` uses the same variable/conversion syntax
2426///   as [`find!`] before checking whether any row survives projection.
2427///
2428/// ```rust,ignore
2429/// exists!(pattern!(&kb, [{ ?person @ social::name: "Alice" }]))
2430/// ```
2431///
2432/// ```rust,ignore
2433/// exists!(
2434///     (name: Inline<_>),
2435///     pattern!(&kb, [{ ?person @ social::name: ?name }])
2436/// )
2437/// ```
2438#[macro_export]
2439macro_rules! exists {
2440    (($($vars:tt)*), $Constraint:expr) => {
2441        $crate::query::find!(($($vars)*), $Constraint).next().is_some()
2442    };
2443    ($Constraint:expr) => {
2444        $crate::query::find!((), $Constraint).next().is_some()
2445    };
2446}
2447/// Re-export of the [`exists!`] macro.
2448pub use exists;
2449
2450/// Introduces one or more temporary query variables for a nested constraint.
2451///
2452/// `temp!` is only meaningful inside macros that provide a local query context,
2453/// such as [`find!`], [`exists!`], or macros expanded from them like
2454/// [`pattern!`](crate::macros::pattern). Each identifier becomes a fresh query
2455/// variable that is scoped to the wrapped body.
2456///
2457/// ```rust,ignore
2458/// find!(
2459///     (person: Inline<_>),
2460///     temp!((friend), and!(
2461///         pattern!(&kb, [{ ?person @ social::friend: ?friend }]),
2462///         pattern!(&kb, [{ ?friend @ social::name: "Bob" }])
2463///     ))
2464/// )
2465/// ```
2466#[macro_export]
2467macro_rules! temp {
2468    (($Var:ident), $body:expr) => {{
2469        let $Var = __local_find_context!().next_variable();
2470        $body
2471    }};
2472    (($Var:ident,), $body:expr) => {
2473        $crate::temp!(($Var), $body)
2474    };
2475    (($Var:ident, $($rest:ident),+ $(,)?), $body:expr) => {{
2476        $crate::temp!(
2477            ($Var),
2478            $crate::temp!(($($rest),+), $body)
2479        )
2480    }};
2481}
2482/// Re-export of the [`temp!`] macro.
2483pub use temp;
2484
2485#[cfg(test)]
2486mod tests {
2487    use inlineencodings::ShortString;
2488
2489    use crate::prelude::inlineencodings::*;
2490    use crate::prelude::*;
2491
2492    use crate::examples::literature;
2493
2494    use fake::faker::lorem::en::Sentence;
2495    use fake::faker::lorem::en::Words;
2496    use fake::faker::name::raw::*;
2497    use fake::locales::*;
2498    use fake::Fake;
2499
2500    use std::collections::HashSet;
2501
2502    use super::*;
2503
2504    fn variable_set(indices: impl IntoIterator<Item = VariableId>) -> VariableSet {
2505        let mut variables = VariableSet::new_empty();
2506        for variable in indices {
2507            variables.set(variable);
2508        }
2509        variables
2510    }
2511
2512    pub mod knights {
2513        use crate::prelude::*;
2514
2515        attributes! {
2516            "8143F46E812E88C4544E7094080EC523" unsafe as loves: inlineencodings::GenId;
2517            "D6E0F2A6E5214E1330565B4D4138E55C" unsafe as name: inlineencodings::ShortString;
2518        }
2519    }
2520
2521    mod shared_level_buffers {
2522        use super::*;
2523
2524        fn value(byte: u8) -> RawInline {
2525            let mut value = [0; 32];
2526            value[31] = byte;
2527            value
2528        }
2529
2530        fn refill(store: &mut BindingStore, values: &[RawInline]) {
2531            store.refill(
2532                0,
2533                &[0],
2534                RowSelection::identity(1),
2535                1,
2536                false,
2537                |frontier, proposals| {
2538                    assert_eq!(frontier.len(), 1);
2539                    proposals.open(0);
2540                    proposals.extend(values.iter().copied());
2541                },
2542            );
2543        }
2544
2545        #[test]
2546        fn clone_shares_published_buffer_but_not_consumption_cursor() {
2547            let mut left = BindingStore::new();
2548            refill(&mut left, &[value(1), value(2), value(3)]);
2549            let right = left.clone();
2550
2551            assert!(Arc::ptr_eq(
2552                left.levels[0].buffer.as_ref().unwrap(),
2553                right.levels[0].buffer.as_ref().unwrap(),
2554            ));
2555
2556            let mut drawn = Vec::new();
2557            let mut parents = Vec::new();
2558            left.draw(0, 1, RowSelection::identity(1), &mut drawn, &mut parents);
2559            assert_eq!(left.levels[0].pos, 1);
2560            assert_eq!(right.levels[0].pos, 0);
2561            assert_eq!(right.levels[0].buffer()[0], value(1));
2562        }
2563
2564        #[test]
2565        fn refill_replaces_a_shared_buffer_without_touching_its_sibling() {
2566            let mut left = BindingStore::new();
2567            refill(&mut left, &[value(1), value(2)]);
2568            let right = left.clone();
2569
2570            refill(&mut left, &[value(9)]);
2571
2572            assert!(!Arc::ptr_eq(
2573                left.levels[0].buffer.as_ref().unwrap(),
2574                right.levels[0].buffer.as_ref().unwrap(),
2575            ));
2576            assert_eq!(&right.levels[0].buffer()[..], &[value(1), value(2)]);
2577            assert_eq!(&left.levels[0].buffer()[..], &[value(9)]);
2578        }
2579
2580        #[test]
2581        fn refill_reuses_a_uniquely_owned_buffer() {
2582            let mut store = BindingStore::new();
2583            refill(&mut store, &[value(1), value(2), value(3)]);
2584            let allocation = Arc::as_ptr(store.levels[0].buffer.as_ref().unwrap());
2585
2586            refill(&mut store, &[value(4)]);
2587
2588            assert_eq!(
2589                Arc::as_ptr(store.levels[0].buffer.as_ref().unwrap()),
2590                allocation,
2591                "unique refill should retain the ProposalBuffer allocation",
2592            );
2593            assert_eq!(&store.levels[0].buffer()[..], &[value(4)]);
2594        }
2595    }
2596
2597    mod social {
2598        use crate::prelude::*;
2599
2600        attributes! {
2601            "A19EC1D9DD534BA9896223A457A6B9C9" unsafe as name: inlineencodings::ShortString;
2602            "C21DE0AA5BA3446AB886C9640BA60244" unsafe as friend: inlineencodings::GenId;
2603        }
2604    }
2605
2606    #[test]
2607    fn and_set() {
2608        let mut books = HashSet::<String>::new();
2609        let mut movies = HashSet::<Inline<ShortString>>::new();
2610
2611        books.insert("LOTR".to_string());
2612        books.insert("Dragonrider".to_string());
2613        books.insert("Highlander".to_string());
2614
2615        movies.insert("LOTR".to_inline());
2616        movies.insert("Highlander".to_inline());
2617
2618        let inter: Vec<_> =
2619            find!((a: Inline<ShortString>), and!(books.has(a), movies.has(a))).collect();
2620
2621        assert_eq!(inter.len(), 2);
2622
2623        let cross: Vec<_> =
2624            find!((a: Inline<ShortString>, b: Inline<ShortString>), and!(books.has(a), movies.has(b))).collect();
2625
2626        assert_eq!(cross.len(), 6);
2627
2628        let one: Vec<_> = find!((a: Inline<ShortString>),
2629            and!(books.has(a), a.is(ShortString::inline_from("LOTR")))
2630        )
2631        .collect();
2632
2633        assert_eq!(one.len(), 1);
2634    }
2635
2636    #[test]
2637    fn pattern() {
2638        let mut kb = TribleSet::new();
2639        (0..1000).for_each(|_| {
2640            let author = fucid();
2641            let book = fucid();
2642            kb += entity! { &author @
2643               literature::firstname: FirstName(EN).fake::<String>(),
2644               literature::lastname: LastName(EN).fake::<String>(),
2645            };
2646            kb += entity! { &book @
2647               literature::author: &author,
2648               literature::title: Words(1..3).fake::<Vec<String>>().join(" "),
2649               literature::quote: Sentence(5..25).fake::<String>().to_blob().get_handle()
2650            };
2651        });
2652
2653        let author = fucid();
2654        let book = fucid();
2655        kb += entity! { &author @
2656           literature::firstname: "Frank",
2657           literature::lastname: "Herbert",
2658        };
2659        kb += entity! { &book @
2660           literature::author: &author,
2661           literature::title: "Dune",
2662           literature::quote: "I must not fear. Fear is the \
2663                   mind-killer. Fear is the little-death that brings total \
2664                   obliteration. I will face my fear. I will permit it to \
2665                   pass over me and through me. And when it has gone past I \
2666                   will turn the inner eye to see its path. Where the fear \
2667                   has gone there will be nothing. Only I will remain.".to_blob().get_handle()
2668        };
2669
2670        (0..100).for_each(|_| {
2671            let author = fucid();
2672            let book = fucid();
2673            kb += entity! { &author @
2674               literature::firstname: "Fake",
2675               literature::lastname: "Herbert",
2676            };
2677            kb += entity! { &book @
2678               literature::author: &author,
2679               literature::title: Words(1..3).fake::<Vec<String>>().join(" "),
2680               literature::quote: Sentence(5..25).fake::<String>().to_blob().get_handle()
2681            };
2682        });
2683
2684        let r: Vec<_> = find!(
2685        (author: Inline<_>, book: Inline<_>, title: Inline<_>, quote: Inline<_>),
2686        pattern!(&kb, [
2687        {?author @
2688            literature::firstname: "Frank",
2689            literature::lastname: "Herbert"},
2690        {?book @
2691          literature::author: ?author,
2692          literature::title: ?title,
2693          literature::quote: ?quote
2694        }]))
2695        .collect();
2696
2697        assert_eq!(1, r.len())
2698    }
2699
2700    #[test]
2701    fn constant() {
2702        let r: Vec<_> = find! {
2703            (string: Inline<_>, number: Inline<_>),
2704            and!(
2705                string.is(ShortString::inline_from("Hello World!")),
2706                number.is(I256BE::inline_from(42))
2707            )
2708        }
2709        .collect();
2710
2711        assert_eq!(1, r.len())
2712    }
2713
2714    #[test]
2715    fn exists_true() {
2716        assert!(exists!((a: Inline<_>), a.is(I256BE::inline_from(42))));
2717    }
2718
2719    #[test]
2720    fn exists_false() {
2721        assert!(!exists!(
2722            (a: Inline<_>),
2723            and!(a.is(I256BE::inline_from(1)), a.is(I256BE::inline_from(2)))
2724        ));
2725    }
2726
2727    #[test]
2728    fn exists_no_variables_true() {
2729        let mut ctx = VariableContext::new();
2730        let a = ctx.next_variable::<I256BE>();
2731        assert!(exists!(a.is(I256BE::inline_from(42))));
2732    }
2733
2734    #[test]
2735    fn find_no_variables_yields_unit() {
2736        let mut ctx = VariableContext::new();
2737        let a = ctx.next_variable::<I256BE>();
2738        let rows: Vec<()> = find!((), a.is(I256BE::inline_from(42))).collect();
2739        assert_eq!(rows, vec![()]);
2740    }
2741
2742    #[test]
2743    fn temp_variables_span_patterns() {
2744        use social::*;
2745
2746        let mut kb = TribleSet::new();
2747        let alice = fucid();
2748        let bob = fucid();
2749
2750        kb += entity! { &alice @ name: "Alice", friend: &bob };
2751        kb += entity! { &bob @ name: "Bob" };
2752
2753        let matches: Vec<_> = find!(
2754            (person_name: Inline<_>),
2755            temp!((mutual_friend),
2756                and!(
2757                    pattern!(&kb, [{ _?person @ name: ?person_name, friend: ?mutual_friend }]),
2758                    pattern!(&kb, [{ ?mutual_friend @ name: "Bob" }])
2759                )
2760            )
2761        )
2762        .collect();
2763
2764        assert_eq!(matches.len(), 1);
2765        assert_eq!(matches[0].0.try_from_inline::<&str>().unwrap(), "Alice");
2766    }
2767
2768    #[test]
2769    fn estimate_override_debug_order() {
2770        use std::cell::RefCell;
2771        use std::rc::Rc;
2772
2773        let mut ctx = VariableContext::new();
2774        let a = ctx.next_variable::<ShortString>();
2775        let b = ctx.next_variable::<ShortString>();
2776
2777        let base = and!(
2778            a.is(ShortString::inline_from("A")),
2779            b.is(ShortString::inline_from("B"))
2780        );
2781
2782        let mut wrapper = crate::debug::query::EstimateOverrideConstraint::new(base);
2783        wrapper.set_estimate(a.index, 10);
2784        wrapper.set_estimate(b.index, 1);
2785
2786        let record = Rc::new(RefCell::new(Vec::new()));
2787        let debug = crate::debug::query::DebugConstraint::new(wrapper, Rc::clone(&record));
2788
2789        let q: Query<_, _, _> = Query::new(debug, |_| Some(()));
2790        let r: Vec<_> = q.collect();
2791        assert_eq!(1, r.len());
2792        assert_eq!(&*record.borrow(), &[b.index, a.index]);
2793    }
2794
2795    #[cfg(feature = "parallel")]
2796    mod split_fence {
2797        use super::*;
2798        use rayon::iter::plumbing::UnindexedProducer;
2799        use rayon::iter::{IntoParallelIterator, ParallelIterator};
2800        use std::sync::atomic::{AtomicU8, Ordering};
2801
2802        #[derive(Clone)]
2803        struct Fixture {
2804            fanout: u32,
2805            anchors: [u8; 4],
2806        }
2807
2808        /// One proposal per level and no alternate continuation anywhere.
2809        /// This is the shape on which speculative splitting is most harmful:
2810        /// cloning a query at every depth prevents the frontier matrices from
2811        /// descending in place even though no parallel work exists.
2812        #[derive(Clone)]
2813        struct OneToOne(usize);
2814
2815        /// Records whether the frontier handed to a constraint carried the
2816        /// explicit parallel-query intent. Bit 0 is sequential, bit 1 is
2817        /// parallel.
2818        #[derive(Clone)]
2819        struct IntentRecorder(Arc<AtomicU8>);
2820
2821        impl<'a> Constraint<'a> for IntentRecorder {
2822            fn variables(&self) -> VariableSet {
2823                variable_set([0])
2824            }
2825
2826            fn estimate(&self, variable: VariableId, _binding: &Binding) -> Option<usize> {
2827                (variable == 0).then_some(1)
2828            }
2829
2830            fn propose(
2831                &self,
2832                _variable: VariableId,
2833                frontier: &Frontier<'_>,
2834                proposals: &mut ProposalBuffer,
2835            ) {
2836                self.0.fetch_or(
2837                    if frontier.parallel() { 0b10 } else { 0b01 },
2838                    Ordering::Relaxed,
2839                );
2840                for row in 0..frontier.len() {
2841                    proposals.open(row as u32);
2842                    proposals.push(Fixture::value(0x55, row as u32));
2843                }
2844            }
2845
2846            fn confirm(
2847                &self,
2848                _variable: VariableId,
2849                _frontier: &Frontier<'_>,
2850                _candidates: &mut Candidates<'_>,
2851            ) {
2852            }
2853        }
2854
2855        impl<'a> Constraint<'a> for OneToOne {
2856            fn variables(&self) -> VariableSet {
2857                variable_set(0..self.0)
2858            }
2859
2860            fn estimate(&self, variable: VariableId, binding: &Binding) -> Option<usize> {
2861                (variable < self.0).then_some(if variable == binding.bound.count() {
2862                    1
2863                } else {
2864                    1 << 20
2865                })
2866            }
2867
2868            fn propose(
2869                &self,
2870                variable: VariableId,
2871                frontier: &Frontier<'_>,
2872                proposals: &mut ProposalBuffer,
2873            ) {
2874                for row in 0..frontier.len() {
2875                    proposals.open(row as u32);
2876                    proposals.push(Fixture::value(0x7f, variable as u32));
2877                }
2878            }
2879
2880            fn confirm(
2881                &self,
2882                _variable: VariableId,
2883                _frontier: &Frontier<'_>,
2884                _candidates: &mut Candidates<'_>,
2885            ) {
2886            }
2887        }
2888
2889        impl Fixture {
2890            fn identity_split(fanout: u32) -> Self {
2891                Self {
2892                    fanout,
2893                    // Anchor 3 is consumed by the scalar latency page. The
2894                    // remaining choices are 1, 2, 3, so stable grouping is
2895                    // already consecutive identity.
2896                    anchors: [3, 0, 1, 2],
2897                }
2898            }
2899
2900            fn nonidentity_split(fanout: u32) -> Self {
2901                Self {
2902                    fanout,
2903                    // After anchor 3, choices are 2, 1, 3 and stable grouping
2904                    // must visit rows 1, 0, 2.
2905                    anchors: [3, 1, 0, 2],
2906                }
2907            }
2908
2909            fn value(tag: u8, index: u32) -> RawInline {
2910                let mut value = [0; 32];
2911                value[0] = tag;
2912                value[28..].copy_from_slice(&index.to_be_bytes());
2913                value
2914            }
2915
2916            fn count(&self, group: u8, variable: VariableId) -> u32 {
2917                match (group, variable) {
2918                    (3 | 0, 1) => 0,
2919                    (1, 2) => self.fanout,
2920                    _ => 1,
2921                }
2922            }
2923        }
2924
2925        impl<'a> Constraint<'a> for Fixture {
2926            fn variables(&self) -> VariableSet {
2927                variable_set(0..4)
2928            }
2929
2930            fn estimate(&self, variable: VariableId, binding: &Binding) -> Option<usize> {
2931                let preferred = binding
2932                    .get(0)
2933                    .map(|anchor| match anchor[31] {
2934                        0 => 1,
2935                        1 => 2,
2936                        2 => 3,
2937                        _ => 1,
2938                    })
2939                    .unwrap_or(0);
2940                (variable < 4).then_some(if variable == 0 {
2941                    4
2942                } else if variable == preferred {
2943                    1
2944                } else {
2945                    8
2946                })
2947            }
2948
2949            fn propose(
2950                &self,
2951                variable: VariableId,
2952                frontier: &Frontier<'_>,
2953                proposals: &mut ProposalBuffer,
2954            ) {
2955                for row in 0..frontier.len() {
2956                    proposals.open(row as u32);
2957                    if variable == 0 {
2958                        proposals.extend(
2959                            self.anchors
2960                                .into_iter()
2961                                .map(|group| Self::value(0, group as u32)),
2962                        );
2963                    } else {
2964                        let group = frontier.row(row).get(0).expect("anchor bound")[31];
2965                        proposals.extend(
2966                            (0..self.count(group, variable))
2967                                .map(|index| Self::value(variable as u8, index)),
2968                        );
2969                    }
2970                }
2971            }
2972
2973            fn confirm(
2974                &self,
2975                _variable: VariableId,
2976                _frontier: &Frontier<'_>,
2977                _candidates: &mut Candidates<'_>,
2978            ) {
2979            }
2980        }
2981
2982        type TestQuery = Query<Fixture, fn(&Binding<'_>) -> Option<RawInline>, RawInline>;
2983
2984        fn project(binding: &Binding<'_>) -> Option<RawInline> {
2985            binding.get(0).copied()
2986        }
2987
2988        fn query(fanout: u32) -> TestQuery {
2989            fixture_query(Fixture::identity_split(fanout))
2990        }
2991
2992        fn nonidentity_query(fanout: u32) -> TestQuery {
2993            fixture_query(Fixture::nonidentity_split(fanout))
2994        }
2995
2996        fn fixture_query(fixture: Fixture) -> TestQuery {
2997            Query::new(fixture, project as fn(&Binding<'_>) -> Option<RawInline>)
2998                .with_frontier_width(4)
2999        }
3000
3001        #[test]
3002        fn only_into_par_iter_sets_parallel_frontier_intent() {
3003            let pool = rayon::ThreadPoolBuilder::new()
3004                .num_threads(4)
3005                .build()
3006                .unwrap();
3007
3008            let sequential = Arc::new(AtomicU8::new(0));
3009            let sequential_rows = pool.install(|| {
3010                Query::new(IntentRecorder(Arc::clone(&sequential)), |_| Some(())).count()
3011            });
3012            assert_eq!(sequential_rows, 1);
3013            assert_eq!(
3014                sequential.load(Ordering::Relaxed),
3015                0b01,
3016                "an ordinary iterator stays serial even inside a Rayon pool"
3017            );
3018
3019            let parallel = Arc::new(AtomicU8::new(0));
3020            let parallel_rows = pool.install(|| {
3021                Query::new(IntentRecorder(Arc::clone(&parallel)), |_| Some(()))
3022                    .into_par_iter()
3023                    .count()
3024            });
3025            assert_eq!(parallel_rows, 1);
3026            assert_eq!(parallel.load(Ordering::Relaxed), 0b10);
3027        }
3028
3029        fn advance_to_three_groups(query: &mut TestQuery) {
3030            query.plan();
3031            query.next_group();
3032            query.next_chunk();
3033
3034            // Exhaust the empty one-row latency frontier, then widen the
3035            // root into three anchors with three preferred variables.
3036            query.plan();
3037            query.next_group();
3038            query.next_chunk();
3039            query.next_group();
3040            query.next_chunk();
3041            query.plan();
3042            assert_eq!(query.depths[query.depth].plan.group_count(), 3);
3043            assert_eq!(query.depths[query.depth].group, 0);
3044            assert_eq!(query.depths[query.depth].group_limit, 3);
3045        }
3046
3047        fn split_plan(query: &TestQuery) -> &SplitPlan {
3048            match &query.depths[query.depth].plan {
3049                DepthPlan::Split(plan) => plan,
3050                plan => panic!("expected split plan, got {plan:?}"),
3051            }
3052        }
3053
3054        #[test]
3055        fn universal_plan_stays_generated_without_choice_storage() {
3056            let mut representation = query(16);
3057            assert_eq!(representation.choice.capacity(), 0);
3058
3059            representation.plan();
3060
3061            assert!(matches!(
3062                representation.depths[0].plan,
3063                DepthPlan::Universal(0)
3064            ));
3065            assert_eq!(
3066                representation.depths[0].selection(),
3067                RowSelection::identity(1)
3068            );
3069            assert_eq!(
3070                representation.choice.capacity(),
3071                0,
3072                "unanimity must not allocate planner choice storage"
3073            );
3074        }
3075
3076        #[test]
3077        fn identity_partition_omits_the_row_order_override() {
3078            let mut representation = query(16);
3079            advance_to_three_groups(&mut representation);
3080
3081            let plan = split_plan(&representation);
3082            assert_eq!(plan.groups, [(1, 1), (2, 2), (3, 3)]);
3083            assert!(plan.order_override.is_empty());
3084            assert_eq!(
3085                representation.depths[representation.depth].selection(),
3086                RowSelection::identity(3)
3087            );
3088        }
3089
3090        #[test]
3091        fn nonidentity_partition_stores_only_the_row_order_override() {
3092            let mut representation = nonidentity_query(16);
3093            advance_to_three_groups(&mut representation);
3094
3095            let plan = split_plan(&representation);
3096            assert_eq!(plan.groups, [(1, 1), (2, 2), (3, 3)]);
3097            assert_eq!(plan.order_override, [1, 0, 2]);
3098            assert_eq!(
3099                representation.depths[representation.depth].selection(),
3100                RowSelection::explicit(&[1, 0, 2])
3101            );
3102
3103            let mut expected: Vec<_> = query(16).collect();
3104            let mut actual: Vec<_> = nonidentity_query(16).collect();
3105            expected.sort_unstable();
3106            actual.sort_unstable();
3107            assert_eq!(
3108                actual, expected,
3109                "row permutation must preserve the exact bag"
3110            );
3111        }
3112
3113        #[test]
3114        fn split_transfers_one_whole_group_and_fences_it() {
3115            const FANOUT: u32 = 64;
3116            let mut expected: Vec<_> = query(FANOUT).collect();
3117            expected.sort_unstable();
3118            assert_eq!(expected.len(), FANOUT as usize + 1);
3119
3120            let mut left = query(FANOUT);
3121            advance_to_three_groups(&mut left);
3122            let source_len = left.bindings.levels[0].buffer().len();
3123            let source_pos = left.bindings.levels[0].pos;
3124            let right = left.split_pending_group().expect("transfer first group");
3125
3126            assert_eq!(
3127                (right.depth, right.depths.len(), right.stack.len()),
3128                (0, 1, 0)
3129            );
3130            assert_eq!(
3131                (
3132                    right.depths[0].group,
3133                    right.depths[0].group_limit,
3134                    right.depths[0].plan.group_count(),
3135                ),
3136                (0, 1, 3)
3137            );
3138            assert!(Arc::ptr_eq(&left.depths[1].block, &right.depths[0].block));
3139            assert_eq!(left.depths[1].selection(), right.depths[0].selection());
3140            match (&left.depths[1].plan, &right.depths[0].plan) {
3141                (DepthPlan::Split(left), DepthPlan::Split(right)) => {
3142                    assert!(Arc::ptr_eq(left, right), "fencing must not clone the plan");
3143                }
3144                plans => panic!("expected shared split plans, got {plans:?}"),
3145            }
3146            assert_eq!(left.depths[1].group, 1, "left skips the owned group");
3147
3148            // The ancestor source was cloned intact, not bisected. Both
3149            // halves retain the same complete proposal region and cursor;
3150            // only their continuation ownership differs.
3151            assert_eq!(left.bindings.levels[0].buffer().len(), source_len);
3152            assert_eq!(right.bindings.levels[0].buffer().len(), source_len);
3153            assert_eq!(left.bindings.levels[0].pos, source_pos);
3154            assert_eq!(right.bindings.levels[0].pos, source_pos);
3155
3156            // A fenced sole root group is indivisible. Trying to split it
3157            // again must enter the work rather than hand the same group back.
3158            let mut fenced = right.clone();
3159            assert!(fenced.split_pending_group().is_none());
3160
3161            let second = left.split_pending_group().expect("transfer second group");
3162            assert!(
3163                left.split_pending_group().is_none(),
3164                "the last group stays with the only producer that can execute it"
3165            );
3166            assert_eq!(left.depths[1].group + 1, left.depths[1].group_limit);
3167
3168            let mut actual: Vec<_> = left.chain(right).chain(second).collect();
3169            actual.sort_unstable();
3170            assert_eq!(actual, expected, "splitting must preserve the exact bag");
3171            assert_eq!(actual.iter().filter(|row| row[31] == 2).count(), 1);
3172        }
3173
3174        fn advance_to_emit(query: &mut TestQuery) {
3175            loop {
3176                match query.mode {
3177                    Search::Plan => query.plan(),
3178                    Search::NextGroup => query.next_group(),
3179                    Search::NextChunk => query.next_chunk(),
3180                    Search::Emit => return,
3181                    Search::Done => panic!("fixture ended before a terminal frontier"),
3182                }
3183            }
3184        }
3185
3186        #[test]
3187        fn terminal_page_is_transferred_without_slicing_rows() {
3188            let mut left = query(32);
3189            advance_to_emit(&mut left);
3190            assert!(left.depth > 0);
3191            let terminal_rows = left.depths[left.depth].rows;
3192            let terminal_block = Arc::clone(&left.depths[left.depth].block);
3193            let terminal_plan = left.depths[left.depth].plan.clone();
3194            let mut expected = left.clone().collect::<Vec<_>>();
3195
3196            let right = left
3197                .split_terminal_frontier()
3198                .expect("terminal page has a parent continuation");
3199            assert_eq!(right.depth, 0);
3200            assert_eq!(right.stack.len(), 0);
3201            assert_eq!(right.mode, Search::Emit);
3202            assert_eq!(right.depths[0].rows, terminal_rows);
3203            assert!(Arc::ptr_eq(&terminal_block, &right.depths[0].block));
3204            assert_eq!(
3205                terminal_plan.selection(terminal_rows),
3206                right.depths[0].selection()
3207            );
3208            for variable in left.bindings.bound {
3209                assert!(Arc::ptr_eq(
3210                    left.bindings.levels[variable]
3211                        .buffer
3212                        .as_ref()
3213                        .expect("bound level has a published buffer"),
3214                    right.bindings.levels[variable]
3215                        .buffer
3216                        .as_ref()
3217                        .expect("bound level has a published buffer"),
3218                ));
3219            }
3220
3221            let mut actual: Vec<_> = left.chain(right).collect();
3222            expected.sort_unstable();
3223            actual.sort_unstable();
3224            assert_eq!(actual, expected);
3225        }
3226
3227        #[test]
3228        fn indivisible_one_to_one_chain_never_splits_or_forces_copies() {
3229            const HOPS: usize = 60;
3230            let query = Query::new(OneToOne(HOPS), |binding: &Binding| {
3231                binding.get(HOPS - 1).copied()
3232            });
3233            let stats = query.stats();
3234
3235            // `UnindexedProducer::split` is allowed to drive the state
3236            // machine looking for a stealable unit. This chain has none:
3237            // every frontier is one sole group/page and every ancestor is
3238            // already spent. It must therefore stay one producer all the
3239            // way down rather than manufacture an empty left sibling at
3240            // each depth.
3241            let (producer, right) = query.into_par_iter().split();
3242            assert!(right.is_none());
3243            let rows = producer.collect::<Vec<_>>();
3244
3245            assert_eq!(rows, vec![Fixture::value(0x7f, HOPS as u32 - 1)]);
3246            assert_eq!(stats.inplace_descents(), HOPS as u64);
3247            assert_eq!(
3248                stats.copied_descents(),
3249                0,
3250                "a fruitless split search must not destroy Arc uniqueness"
3251            );
3252        }
3253
3254        #[test]
3255        fn repeated_manual_producer_splits_preserve_explicit_order_and_bag() {
3256            let pool = rayon::ThreadPoolBuilder::new()
3257                .num_threads(4)
3258                .build()
3259                .unwrap();
3260
3261            for fixture in [query as fn(u32) -> TestQuery, nonidentity_query] {
3262                let mut expected: Vec<_> = fixture(96).collect();
3263                expected.sort_unstable();
3264
3265                let mut queue = vec![fixture(96).into_par_iter()];
3266                let mut leaves = Vec::new();
3267                for _ in 0..64 {
3268                    let Some(producer) = queue.pop() else {
3269                        break;
3270                    };
3271                    let (left, right) = producer.split();
3272                    if let Some(right) = right {
3273                        queue.push(left);
3274                        queue.push(right);
3275                    } else {
3276                        leaves.push(left);
3277                    }
3278                }
3279
3280                let mut actual = Vec::new();
3281                for producer in queue.into_iter().chain(leaves) {
3282                    actual.extend(producer.collect::<Vec<_>>());
3283                }
3284                actual.sort_unstable();
3285                assert_eq!(actual, expected);
3286
3287                for _ in 0..8 {
3288                    let mut scheduled =
3289                        pool.install(|| fixture(96).into_par_iter().collect::<Vec<_>>());
3290                    scheduled.sort_unstable();
3291                    assert_eq!(scheduled, expected);
3292                }
3293            }
3294        }
3295    }
3296}