pub struct GraphIndex { /* private fields */ }Expand description
The six tiled permutation sections, queryable by triple pattern.
Implementations§
Source§impl GraphIndex
impl GraphIndex
Sourcepub fn from_tiles(
sections: [Vec<(u32, u32, Vec<u8>)>; 6],
perms: PermSet,
) -> GraphIndex
pub fn from_tiles( sections: [Vec<(u32, u32, Vec<u8>)>; 6], perms: PermSet, ) -> GraphIndex
Rebuild from tiled sections: per permutation, (min_a, max_a, block bytes) per tile in ascending leading-id order — the v0.2 layout.
Slots outside perms must be empty.
Sourcepub fn set_cache_cap(&self, cap: u64)
pub fn set_cache_cap(&self, cap: u64)
Set the byte cap of this index’s tile cache (bounded-export phase 2).
u64::MAX (the open-time default) is unlimited — a scanned permutation
stays resident exactly as before. A finite cap evicts least-recently-used
tiles, so a full scan’s tile residency is bounded.
Only safe on an index whose tiles can be re-faulted (a remote/ranged index with a loader). A locally-built index has no loader — an evicted tile could never be reloaded — so the caller must not cap those; the bounded-export path caps only the ranged default-graph index and leaves resident named-graph indexes unlimited (released per slot instead).
Sourcepub fn is_lazy(&self) -> bool
pub fn is_lazy(&self) -> bool
Does this index fault its tiles through a loader (a remote/ranged open)? Only such an index is safe to give a finite tile-cache cap: a locally decoded index has no loader, so an evicted tile could never be reloaded.
Sourcepub fn cache_stats(&self) -> CacheStats
pub fn cache_stats(&self) -> CacheStats
A snapshot of the tile cache’s observability counters.
Sourcepub fn from_remote_directories(
directories: [Vec<(u32, u32, Option<(u32, u32, u32, u32)>)>; 6],
perms: PermSet,
loader: Box<dyn Fn(usize, usize) -> Option<Vec<u8>> + Sync + Send>,
) -> GraphIndex
pub fn from_remote_directories( directories: [Vec<(u32, u32, Option<(u32, u32, u32, u32)>)>; 6], perms: PermSet, loader: Box<dyn Fn(usize, usize) -> Option<Vec<u8>> + Sync + Send>, ) -> GraphIndex
A remote index: only the tile directories (leading-id ranges per
permutation, ascending) are known; tile payloads fault in through
loader on first scan. Check load_incomplete
after evaluating — a failed fetch must become an error, never a
silently smaller result.
Sourcepub fn with_bulk_loader(
self,
bulk: Box<dyn Fn(usize, &[usize]) -> Option<Vec<Vec<u8>>> + Sync + Send>,
) -> GraphIndex
pub fn with_bulk_loader( self, bulk: Box<dyn Fn(usize, &[usize]) -> Option<Vec<Vec<u8>>> + Sync + Send>, ) -> GraphIndex
Attach a batched tile fetcher (see [TileBulkLoader]): multi-tile
scans prefetch their span through it instead of faulting tile by tile.
Sourcepub fn load_incomplete(&self) -> bool
pub fn load_incomplete(&self) -> bool
Did any tile fetch fail since this index was opened — or since the last
reset_load_failure?
Sourcepub fn reset_load_failure(&self)
pub fn reset_load_failure(&self)
Forget recorded fetch failures — the start-of-evaluation reset for a RESIDENT session, making the incompleteness verdict per-query instead of per-open (one transient network blip used to fail every later query on the session). Safe because failed tiles are never cached: the next scan simply retries them.
Sourcepub fn is_remote(&self) -> bool
pub fn is_remote(&self) -> bool
True for a remote/lazy index (tiles fault in over a RangeReader). The
join planner uses this to pick read-aware strategies: here a per-row index
probe is a network round-trip, not a memory lookup, so a left-deep scan +
hash join often beats probing a moderately-sized prefix.
Sourcepub fn triple_count(&self) -> u32
pub fn triple_count(&self) -> u32
Total triple count (sum of the SPO tiles’ zone counts). For a remote index this faults in the SPO tiles — prefer the header’s quad count.
Sourcepub fn tile_sections(&self) -> [&[Tile]; 6]
pub fn tile_sections(&self) -> [&[Tile]; 6]
The tiles of each permutation section (in ALL_PERMS order), for the
file writer.
Sourcepub fn best_permutation(
pattern: (Option<u32>, Option<u32>, Option<u32>),
) -> IndexPermutation
pub fn best_permutation( pattern: (Option<u32>, Option<u32>, Option<u32>), ) -> IndexPermutation
The permutation selected for a pattern: the one with the longest bound prefix. Ties keep the canonical SPO order (then POS, then OSP), which makes provenance stable for unbound or equally selective shapes and routes a fully unbound pattern to the SPO block rather than fetching all three.
Six-permutation shorthand for best_permutation_in. Its answer is
always inside PermSet::CORE (see perm_routing_never_leaves_core),
so a file with any legal PermSet routes identically — but call the
_in form wherever the file’s set is at hand, so that stays true by
construction rather than by argument.
Sourcepub fn best_permutation_in(
perms: PermSet,
pattern: (Option<u32>, Option<u32>, Option<u32>),
) -> IndexPermutation
pub fn best_permutation_in( perms: PermSet, pattern: (Option<u32>, Option<u32>, Option<u32>), ) -> IndexPermutation
best_permutation restricted to the
permutations a file carries.
Sourcepub fn permutation_sorted_on(
pattern: (Option<u32>, Option<u32>, Option<u32>),
sort_col: usize,
) -> Option<IndexPermutation>
pub fn permutation_sorted_on( pattern: (Option<u32>, Option<u32>, Option<u32>), sort_col: usize, ) -> Option<IndexPermutation>
Choose a permutation that routes on pattern’s bound prefix and
streams sorted on the canonical column sort_col (0=subject, 1=predicate,
2=object) — i.e. sort_col is the leading free component after the bound
prefix. Returns None if sort_col is itself bound (then every row shares
that value — any permutation is “sorted” on it) or no permutation qualifies.
This is the precondition a merge join needs: both inputs sorted on the join
key. Among qualifiers, the longest bound prefix (best routing) wins.
Six-permutation shorthand for permutation_sorted_on_in.
Sourcepub fn permutation_sorted_on_in(
perms: PermSet,
pattern: (Option<u32>, Option<u32>, Option<u32>),
sort_col: usize,
) -> Option<IndexPermutation>
pub fn permutation_sorted_on_in( perms: PermSet, pattern: (Option<u32>, Option<u32>, Option<u32>), sort_col: usize, ) -> Option<IndexPermutation>
permutation_sorted_on restricted
to the permutations a file carries — and it will not trade routing for
sort order.
A qualifier must match the bound prefix that
best_permutation_in achieves.
With all six that costs nothing: every ordering of (s, p, o) exists, so
“the bound components first, then sort_col” is always available at the
maximal prefix. With PermSet::CORE it is what stops a merge join from
buying its co-sorted stream with a whole-section scan — e.g.
?s :p ?o sorted on ?s has no P*S order left, and the only remaining
sorted-on-subject stream is an unrouted SPO scan of the entire graph. The
merge seed then declines and the hash/probe path answers, which is
strictly the cheaper plan.
Sourcepub fn match_serialized_block(
bytes: &[u8],
permutation: IndexPermutation,
pattern: (Option<u32>, Option<u32>, Option<u32>),
) -> Vec<(u32, u32, u32)>
pub fn match_serialized_block( bytes: &[u8], permutation: IndexPermutation, pattern: (Option<u32>, Option<u32>, Option<u32>), ) -> Vec<(u32, u32, u32)>
Match one already-decoded serialized permutation block (a single v0.1
section or one v0.2 tile). This is the core primitive for range-routed
readers: the caller fetches only the selected payload, then this scans
it as if it came from a full GraphIndex.
Sourcepub fn match_pattern(
&self,
pattern: (Option<u32>, Option<u32>, Option<u32>),
) -> Vec<(u32, u32, u32)>
pub fn match_pattern( &self, pattern: (Option<u32>, Option<u32>, Option<u32>), ) -> Vec<(u32, u32, u32)>
All triples matching pattern, returned in canonical (s, p, o) order.
Thin eager wrapper over scan_iter: collect the lazy
stream and restore the canonical sort (the stream is sorted in the chosen
permutation’s order, which differs from canonical once perm.back
permutes the free components).
Sourcepub fn scan_iter(
&self,
pattern: (Option<u32>, Option<u32>, Option<u32>),
) -> impl Iterator<Item = (u32, u32, u32)>
pub fn scan_iter( &self, pattern: (Option<u32>, Option<u32>, Option<u32>), ) -> impl Iterator<Item = (u32, u32, u32)>
Lazily stream the triples matching pattern in canonical (s, p, o)
order within the chosen permutation — the streaming entry point for
callers that can stop early (ASK, LIMIT, BGP probes) or that don’t need
the canonical re-sort. Decodes only the matching groups (see
TripleBlock::scan); a malformed/absent block yields nothing rather
than panicking. The permutation is chosen for the longest bound prefix.
Sourcepub fn scan_plan(
&self,
pattern: (Option<u32>, Option<u32>, Option<u32>),
) -> ScanPlan
pub fn scan_plan( &self, pattern: (Option<u32>, Option<u32>, Option<u32>), ) -> ScanPlan
What scan_iter would fetch for pattern, computed
from the tile directories alone — no tile is fetched, decoded, or even
looked at. This is the cost-preview half of the routed scan: it walks
the same three decisions the scan makes (permutation, routed tile span,
synopsis prune) and reports what survives, so a caller can say “this dump
will pull 16 MB of index, not 376 MB” before starting it.
It is a preview, not a promise: tile_bytes is an upper bound on the
index bytes the scan reads (an admitted tile can still be rejected by its
in-tile zone map once in hand, and a re-scan of an already-faulted tile
reads nothing), and it says nothing about the dictionary, which is where
a term-resolving dump’s real cost usually lives.
Sourcepub fn scan_batch(
&self,
pattern: (Option<u32>, Option<u32>, Option<u32>),
cursor: u64,
max_rows: usize,
) -> (Vec<(u32, u32, u32)>, u64, bool)
pub fn scan_batch( &self, pattern: (Option<u32>, Option<u32>, Option<u32>), cursor: u64, max_rows: usize, ) -> (Vec<(u32, u32, u32)>, u64, bool)
One bounded, resumable slice of pattern’s matches: the pull form of
scan_iter, for a caller that cannot hold a borrow
across calls.
Returns (triples, next_cursor, done); start at cursor = 0 and feed the
returned cursor back until done. The whole resume state is one opaque
u64 — (tile index, next a-group id) — so no iterator, no thread and no
self-referential struct has to survive between calls. That is the shape a
foreign-function boundary needs: a wasm module cannot suspend a Rust
iterator mid-scan and hand control back to its host, but it can be handed
a u64 and told to carry on.
max_rows is a floor, not a hard cut: a batch always ends on an
a-group boundary of the chosen permutation, so no group is ever split
across two calls and the overshoot is one group’s fanout. Nothing is ever
rescanned (TripleBlock::scan_resume jumps straight to the group
through the tile’s directory), so a full drain is O(n) overall — unlike
(offset, limit), which is O(n²/limit).
Every call either yields at least one row or reports done, so a caller
never has to guard against an empty non-final batch.
Order. Rows arrive in the chosen permutation’s order and each batch is
re-sorted canonically, exactly as match_pattern
does for a whole result. For a fully unbound pattern (routed to SPO) that
makes the concatenation of every batch identical to match_pattern’s
output; for a bound pattern the set is identical but the canonical
re-sort is per batch rather than global.
Memory is O(max_rows + one a-group + the tiles faulted so far) —
never O(matches). That is the whole point: an unbounded (?s ?p ?o) over
a 26-million-quad graph used to materialize every match before the caller
saw one row.