WeaveDocs
Gnosis

Pattern Query

Query Gnosis with subject, predicate, and object wildcards.

What this page covers

The query method, the wildcard model, the index intersection logic, and the cost model.

QueryPattern

pub struct QueryPattern {
    pub subject:   Option<String>,
    pub predicate: Option<String>,
    pub object:    Option<String>,
}

None is a wildcard; Some(value) is an exact match. The three positions form an AND — every constraint must hold.

Examples

use std::sync::Arc;
use tokio::sync::RwLock;
use strand::{Strand, StrandConfig};
use gnosis::{Gnosis, QueryPattern};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let dir = tempfile::tempdir()?;
    let cfg = StrandConfig::new().with_storage(dir.path());
    let strand = Arc::new(RwLock::new(Strand::new(cfg).await?));
    let g = Gnosis::new(strand);

    g.add_triple("alice", "trusts",   "bob").await?;
    g.add_triple("alice", "trusts",   "carol").await?;
    g.add_triple("bob",   "has_role", "admin").await?;
    g.add_triple("carol", "has_role", "viewer").await?;

    // All facts about alice.
    let r = g.query(&QueryPattern {
        subject:   Some("alice".into()),
        predicate: None,
        object:    None,
    }).await?;
    assert_eq!(r.len(), 2);

    // All trust relationships.
    let r = g.query(&QueryPattern {
        subject:   None,
        predicate: Some("trusts".into()),
        object:    None,
    }).await?;
    assert_eq!(r.len(), 2);

    // Everyone who trusts bob.
    let r = g.query(&QueryPattern {
        subject:   None,
        predicate: Some("trusts".into()),
        object:    Some("bob".into()),
    }).await?;
    assert_eq!(r[0].subject, "alice");

    // Exact fact existence check.
    let r = g.query(&QueryPattern {
        subject:   Some("alice".into()),
        predicate: Some("trusts".into()),
        object:    Some("bob".into()),
    }).await?;
    assert!(!r.is_empty());

    Ok(())
}

How matching works

  1. For each Some(value) position, look up the matching sequence-number set in the corresponding DashMap.
  2. Intersect the sets (smallest first, in practice — the current implementation builds the working set incrementally).
  3. For each surviving sequence number, fetch the bytes from the Strand and decode the Triple.
  4. Return the resulting Vec<Triple>.

Wildcard-only queries (all three positions None) walk the entire Strand and decode every block. This is O(N) and should be reserved for diagnostics or full exports.

Cost model

Query shapeCost (N = total triples)
(S, _, _)O(facts_about_S) strand reads
(_, P, _)O(facts_with_predicate_P) strand reads
(_, _, O)O(facts_about_O) strand reads
(S, P, _)O(min(set_S, set_P)) after intersection
(S, P, O)O(1) typical, exact existence check
(_, _, _)O(N) full scan

Returning structured results

The current API returns Vec<Triple>. Pair the call with a small adapter to your domain shape:

use gnosis::{Gnosis, QueryPattern};

struct TrustEdge { pub from: String, pub to: String }

async fn list_trust_edges(g: &Gnosis, from: &str) -> Result<Vec<TrustEdge>, Box<dyn std::error::Error>> {
    let triples = g.query(&QueryPattern {
        subject:   Some(from.to_string()),
        predicate: Some("trusts".to_string()),
        object:    None,
    }).await?;

    Ok(triples
        .into_iter()
        .map(|t| TrustEdge { from: t.subject, to: t.object })
        .collect())
}

Limits today

CapabilityTodayWorkaround
Wildcard prefix matchNoMaterialize index entries with prefix keys
Disjunction (P = X OR Y)NoIssue two queries and merge
NegationNoFilter in the caller
Ordering / paginationNo (returns all matches)Sort and paginate in the caller
Streaming resultsNo (collects into Vec)Use the Strand directly for very large result sets

Recipes

"What does Alice know?"

g.query(&QueryPattern {
    subject:   Some("alice".into()),
    predicate: None,
    object:    None,
}).await?

"Who has the admin role?"

g.query(&QueryPattern {
    subject:   None,
    predicate: Some("has_role".into()),
    object:    Some("admin".into()),
}).await?

"Does this fact exist?"

let exists = !g.query(&QueryPattern {
    subject:   Some(s.into()),
    predicate: Some(p.into()),
    object:    Some(o.into()),
}).await?.is_empty();