query-flow
A high-level query framework for incremental computation in Rust.
[!WARNING] This is WIP
Features
- Async-agnostic queries: Write sync query logic, run with sync or async runtime
- Automatic caching: Query results are cached and invalidated based on dependencies
- Suspense pattern: Handle async loading with
LoadingStatewithout coloring functions - Type-safe: Per-query-type caching with compile-time guarantees
- Early cutoff: Skip downstream recomputation when values don't change
- Lock-free: Built on whale, a lock-free dependency-tracking primitive
Quick Start
use ;
let runtime = new;
let result = runtime.query.unwrap;
assert_eq!;
Defining Queries
Using the #[query] Macro
The #[query] macro transforms a function into a query struct implementing the Query trait:
use ;
// Basic query - generates `Add` struct
// Query with dependencies
Macro Options
// Custom durability (0-3, higher = changes less frequently)
// Selective cache keys - only `id` is part of the key
// Custom struct name
// Custom output equality (for types without PartialEq)
Manual Query Implementation
For full control, implement the Query trait directly:
use ;
Error Handling
query-flow supports both system errors and user errors through QueryError:
- System errors:
Suspend,Cycle,Cancelled,MissingDependency - User errors:
UserError(Arc<anyhow::Error>)- cached like successful results
// User errors with ? operator - errors are automatically converted
// System errors propagate automatically
Error Comparator for Early Cutoff
By default, all UserError values are considered different (conservative). Use QueryRuntimeBuilder to customize:
let runtime = builder
.error_comparator
.build;
Assets: External Inputs
Assets represent external resources (files, network data) that queries can depend on:
Defining Asset Keys
use ;
use PathBuf;
// Using the macro
;
// With durability hint
;
// Manual implementation
;
Using Assets in Queries
Asset Loading Flow
let runtime = new;
// Optional: Register a locator for immediate resolution
runtime.register_asset_locator;
// Execute query - may return Err(Suspend) if assets are loading
match runtime.query
Asset Invalidation
// File was modified externally
runtime.invalidate_asset;
// Dependent queries will now suspend until resolved
// Remove asset entirely
runtime.remove_asset;
Suspense Pattern
The suspense pattern allows sync query code to handle async operations:
/// LoadingState<T> represents async loading state
Durability Levels
Durability hints help optimize invalidation propagation:
| Level | Value | Description |
|---|---|---|
Volatile |
0 | Changes frequently (default) |
Session |
1 | Stable within a session |
Stable |
2 | Changes rarely |
Constant |
3 | Never changes (bundled assets) |
QueryRuntime API
let runtime = new;
// Execute queries
let result = runtime.query?;
// Invalidation
runtime.;
runtime.clear_cache;
// Asset management
runtime.register_asset_locator;
runtime.resolve_asset;
runtime.invalidate_asset;
runtime.remove_asset;
// Pending assets
runtime.pending_assets; // All pending
runtime.; // Filtered by type
runtime.has_pending_assets;
Crates
| Crate | Description |
|---|---|
query-flow |
High-level query framework with automatic caching and dependency tracking |
query-flow-macros |
Procedural macros for defining queries |
query-flow-inspector |
Debugging and inspection tools |
whale |
Low-level lock-free dependency-tracking primitive |
Whale
Whale is the low-level primitive that powers query-flow. It provides lock-free dependency tracking without opinions about what queries are or how to store their results.
When to Use Whale Directly
Use query-flow if you want a batteries-included incremental computation framework. Use whale directly if you need:
- Full control over query representation and storage
- Custom invalidation strategies
- Integration with existing systems
- Maximum flexibility
Whale Design
Whale is designed to be a minimal primitive for building high-level incremental computing systems. It does not provide:
- What actually the "query" is
- How to calculate a query ID
- Any data storage to store the result of a query
- Rich high-level APIs
Whale Architecture
Whale is built around a lock-free dependency graph where nodes represent computations and edges represent their dependencies.
Core Components:
- Runtime: The central coordinator that manages the dependency graph. Lock-free and safe to clone across threads.
- Node: A vertex representing a computation with version, dependencies, dependents, and invalidation state.
- Pointer: A reference to a specific version of a computation (query ID + version).
- RevisionPointer: An extended pointer including invalidation state for precise state tracking.
Lock-free Design:
The system uses atomic operations and immutable data structures:
- Nodes are updated through atomic compare-and-swap operations
- Dependencies and dependents are stored in immutable collections
- Version numbers are managed through atomic counters
This allows multiple threads to concurrently query states, propagate invalidations, and modify the dependency graph.
Consistency Guarantees:
- Version Monotonicity: Version numbers only increase per query
- Cyclic Safety: Remains functional even with cycles in the dependency graph
- Invalidation Guarantees: All dependents are notified of changes
Alternatives
- salsa: A well-known library for incremental computing with a different design philosophy.
License
Licensed under either of
- Apache License, Version 2.0
- MIT license
at your option.