Skip to main content

GraphIndex

Struct GraphIndex 

Source
pub struct GraphIndex { /* private fields */ }
Expand description

The six tiled permutation sections, queryable by triple pattern.

Implementations§

Source§

impl GraphIndex

Source

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.

Source

pub fn perms(&self) -> PermSet

The permutations this index carries.

Source

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).

Source

pub fn cache_cap(&self) -> u64

The current tile-cache byte cap (u64::MAX = unlimited).

Source

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.

Source

pub fn cache_stats(&self) -> CacheStats

A snapshot of the tile cache’s observability counters.

Source

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.

Source

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.

Source

pub fn load_incomplete(&self) -> bool

Did any tile fetch fail since this index was opened — or since the last reset_load_failure?

Source

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.

Source

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.

Source

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.

Source

pub fn tile_sections(&self) -> [&[Tile]; 6]

The tiles of each permutation section (in ALL_PERMS order), for the file writer.

Source

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.

Source

pub fn best_permutation_in( perms: PermSet, pattern: (Option<u32>, Option<u32>, Option<u32>), ) -> IndexPermutation

best_permutation restricted to the permutations a file carries.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V