1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//! Closure-probe eligibility — the single description of when
//! `MATCH (n:ClosedSupertype {p: v})` can be answered by per-member index
//! lookups instead of a scan.
//!
//! Two consumers read this module: the matcher, which runs the probe
//! ([`super::matcher::PatternExecutor::try_closure_probe`]), and
//! `generate_explain_result`, which renders the `ClosureProbe` plan row.
//! Keeping the enumeration and the coverage rule here is what stops the plan
//! and the execution drifting apart — EXPLAIN renders from
//! [`closure_probe_members`], and the matcher shares
//! [`live_closure_members`] with it.
//!
//! [`closure_probe_members`] is deliberately **value-independent**: it answers
//! "is every member's lookup servable by an index", never "does this value
//! exist". A value miss is a property of the data, not of the plan.
use crateDirGraph;
/// The member types a closure probe on `node_type` would visit: every declared
/// descendant of `node_type` plus `node_type` itself, restricted to those with
/// at least one live node of that primary type. Declaration order is
/// `ontology.classes`' (a `BTreeMap`), so the sequence is deterministic, with
/// `node_type` last.
///
/// Liveness matters because a probe over a member with no nodes contributes
/// nothing but still demands index coverage the type may never have been given.
pub
/// Whether a closure probe on `node_type` with equality predicates on
/// `props` is eligible, and over which member types it would run.
///
/// Eligible when *all* of:
/// - `node_type` is a **Closed** managed label — the engine is the bucket's
/// only writer, so the per-member probes are the complete answer. An `Open`
/// bucket may hold carriers no descendant probe covers.
/// - at least one live member exists (see [`live_closure_members`]).
/// - every live member resolves every property in `props`: an equality index
/// that can answer a point lookup of it (`index_answers_point_lookup` —
/// alias-aware, and excluding the soft-alias names whose index contents are
/// a subset of what a scan matches), or `prop` being that member's id field
/// (canonical `id`, or its `id_field_aliases` entry). Coverage must be
/// total — a partial union would silently drop rows.
///
/// `props` are property *names*, because EXPLAIN sees an AST, not resolved
/// `PropertyMatcher` values. This gate proves declaration coverage only;
/// runtime must also accept every query value or fall back for the full union.
///
/// Deliberately narrower than what `try_index_lookup` can serve: a composite
/// index over the pattern's whole property set, a range index, and a
/// disk-persistent property index all answer at runtime but do not count as
/// coverage here. Each would have to be re-checked value-side (the persistent
/// store is string-only and reports an all-null block as "no index"), and the
/// shared predicate describes structural eligibility, not final value-side
/// admission. A runtime decline must never become a partial union.
pub
/// A point lookup of `prop` on `node_type` resolves without a scan.
///
/// The id field needs no user index: every type carries a self-healing id map
/// (`lookup_by_id_readonly`), and `resolve_alias` maps the type's registered
/// id spelling onto it. Everything else must be index-answerable.
///
/// Shared with the label-alternation probe
/// (`matcher::PatternExecutor::try_alternation_probe`), which asks the same
/// all-or-nothing question of a branch label that this module asks of a
/// closure member — one coverage rule, so the two paths cannot come to
/// different conclusions about what an index can answer.
pub