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
- For each
Some(value)position, look up the matching sequence-number set in the correspondingDashMap. - Intersect the sets (smallest first, in practice — the current implementation builds the working set incrementally).
- For each surviving sequence number, fetch the bytes from the Strand and decode the
Triple. - 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 shape | Cost (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
| Capability | Today | Workaround |
|---|---|---|
| Wildcard prefix match | No | Materialize index entries with prefix keys |
Disjunction (P = X OR Y) | No | Issue two queries and merge |
| Negation | No | Filter in the caller |
| Ordering / pagination | No (returns all matches) | Sort and paginate in the caller |
| Streaming results | No (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();