# Query Engine

**Last Updated:** 2026-02-01
**Confidence:** High
**Status:** Implemented in `stemedb-query` v0.1.0

## Summary

The Query Engine provides lifecycle-aware filtering for reading assertions from the knowledge graph.

**Key Facts:**
- Filters by subject, predicate, lifecycle, epoch
- **Fast path**: checks `MV:{subject}:{predicate}` for pre-computed winner (O(1))
- **MV staleness control**: `max_stale` parameter to reject stale materialized views
- **Compound index**: `SP:{subject}:{predicate}` for subject+predicate lookups (O(1))
- **Subject index**: `S:{subject}` for subject-only lookups (O(1))
- Falls back to full scan when no index available

**File Pointers:**
- `crates/stemedb-query/src/engine.rs` - QueryEngine with fast-path and staleness check
- `crates/stemedb-query/src/query.rs` - Query and QueryResult types

## Query Structure

```rust
pub struct Query {
    pub subject: Option<String>,
    pub predicate: Option<String>,
    pub lifecycle: Option<LifecycleStage>,
    pub epoch: Option<EpochId>,
    pub limit: Option<usize>,
    pub max_stale: Option<u64>,  // Maximum MV age in seconds
}
```

## Usage

```rust
let query = Query::builder()
    .subject("Tesla")
    .predicate("revenue")
    .lifecycle(LifecycleStage::Approved)
    .max_stale(300)  // Only use MVs < 5 minutes old
    .limit(10)
    .build();

let result = engine.execute(&query).await?;
```

## MV Staleness Control

The `max_stale` parameter controls whether the fast path (materialized view) is used:

| `max_stale` | Behavior |
|-------------|----------|
| `None` (default) | Accept any MV age (backward compatible) |
| `Some(0)` | Only accept brand-new MVs (age = 0 seconds) |
| `Some(60)` | Only use MV if materialized within last 60 seconds |
| `Some(300)` | Only use MV if materialized within last 5 minutes |

When the MV is stale, the query falls through to the slow path (SP index lookup + lens resolution).

**Use cases:**
- Real-time dashboards: `max_stale(60)` for fresh data
- Audit investigations: `max_stale(0)` to ensure current state
- Batch processing: `None` for maximum performance

## Query Execution Strategy

0. **Fast path** (subject + predicate): `MV:{subject}:{predicate}` → `MaterializedView.winner` (O(1))
   - Skips if MV doesn't exist
   - Skips if MV is too stale (`max_stale` check)
   - Skips if winner doesn't match filters (lifecycle, epoch)
1. **Compound index** (subject + predicate): `SP:{subject}:{predicate}` → hashes (O(1))
2. **Subject index** (subject only): `S:{subject}` → hashes (O(1))
3. **Full scan** (no subject): Scan `H:` prefix (O(n))
4. **Filter**: Apply lifecycle/epoch filters on candidates
5. **Limit**: Truncate if limit specified

## Related Topics

- [Materializer](./materializer.md) - Produces the MV: keys used by fast path
- [Assertion](./assertion.md)
- [Lifecycle Stages](./lifecycle.md)
- [Lens Resolution](./lens.md)
