resolver.rs
Overview
This file defines a data loader for efficiently fetching blockchain block data from a SQLite database. It implements the Loader trait from the async_graphql::dataloader module to batch-load blocks by their IDs asynchronously. This approach optimizes data retrieval by reducing the number of database queries required when resolving GraphQL requests that need multiple blocks.
The primary functionality revolves around the BlockLoader struct, which holds a database connection pool and provides an asynchronous method to load blocks corresponding to a collection of block IDs. The blocks fetched are mapped by their IDs and returned in a hash map, enabling quick lookups for subsequent GraphQL resolvers.
Structs and Implementations
BlockLoader
pub struct BlockLoader {
pub pool: SqlitePool,
}
Purpose:
Holds a connection pool to the SQLite database for executing SQL queries.Fields:
pool: SqlitePool— A connection pool managing SQLite database connections, allowing concurrent asynchronous queries.
Usage Example:
let pool = SqlitePool::connect("sqlite://mydb.sqlite").await?;
let block_loader = BlockLoader { pool };
Loader<String> Implementation for BlockLoader
The BlockLoader implements the Loader trait for the key type String, where each key is a block ID represented as a String.
impl Loader<String> for BlockLoader {
type Error = Error;
type Value = super::Block;
async fn load(
&self,
keys: &[String],
) -> anyhow::Result<HashMap<String, Self::Value>, Self::Error> {
// Implementation here
}
}
Associated Types:
Error— The error type isasync_graphql::Error.Value— The returned value type per key issuper::Block, representing a blockchain block in the system.
Method:
load
async fn load(
&self,
keys: &[String],
) -> anyhow::Result<HashMap<String, Self::Value>, Self::Error>
Parameters:
keys: &[String]— A slice of block ID strings for which blocks need to be loaded.
Returns:
An asynchronous result resolving to aHashMap<String, Block>, where each key is a block ID from the input slice, and the value is the correspondingBlockstruct.Functionality:
Converts the list of block IDs (
keys) into a comma-separated string suitable for an SQLINclause.Constructs a SQL query string to select all blocks matching the provided IDs.
Executes the query asynchronously using
sqlxwith the SQLite connection pool.Uses a stream to map database rows (
db::Block) into the application domain modelBlock(super::Block).Collects the results into a hash map keyed by block ID.
Returns the hash map or an error if the query fails.
Implementation Details:
Uses
tracing::trace!for detailed SQL query logging under the "data_loader" target.Relies on
sqlx::query_asto map SQL rows directly intodb::Block.Converts database
Blockinstances into domainBlockvia theFromtrait or similar conversion.Uses
futures::TryStreamExtto handle asynchronous stream of query results efficiently.
Example Usage:
let block_ids = vec!["block1".to_string(), "block2".to_string()];
let loaded_blocks = block_loader.load(&block_ids).await?;
if let Some(block) = loaded_blocks.get("block1") {
println!("Block found: {:?}", block);
}
Important Implementation Details and Algorithms
Batch Loading with SQL
INClause:
To optimize multiple block retrievals, the loader constructs a single SQL query using theINclause to fetch all blocks whose IDs are in the requested list. This reduces the number of database round-trips compared to loading each block individually.Asynchronous Streaming and Collection:
The database rows are fetched as a stream and asynchronously converted, which allows efficient handling of large result sets without blocking.Type Conversion:
The conversion fromdb::Block(database model) tosuper::Block(domain model) abstracts database schema details from the GraphQL layer, encapsulating data transformation logic.Tracing Integration:
SQL statements executed are logged at trace level for debugging and performance monitoring, enhancing observability of data access patterns.
Interaction with Other System Components
super::BlockStruct:
The loader returns instances ofBlockdefined in the parent module. This struct represents the blockchain block entity exposed through the GraphQL API and likely includes fields such as block ID, timestamp, transactions, etc.db::BlockStruct:
Represents the database schema for blocks and is used to map SQL query results. The loader converts this into the domainBlockrepresentation.SqlitePoolfromsqlx:
Provides the connection management layer for asynchronous database operations.async_graphql::dataloader::Loader:
The loader trait implemented enables integration with the GraphQL data loader system, facilitating batched and cached data fetching during query resolution.Logging via
tracing:
The loader uses thetracingcrate for structured logging, which integrates with system-wide observability tools.
Diagram: Structure and Workflow of BlockLoader
flowchart TD
A[BlockLoader] -->|has| B[SqlitePool]
A -->|implements| C[Loader<String>]
C -->|method| D(load)
D --> E[Receives block IDs]
E --> F[Constructs SQL query with IN clause]
F --> G[Executes async query via SqlitePool]
G --> H[Maps db::Block rows to super::Block]
H --> I[Collects results into HashMap<String, Block>]
I --> J[Returns Map of block ID -> Block]
This flowchart illustrates how the BlockLoader uses its SqlitePool to execute an asynchronous query that loads blocks by their IDs, converts database rows to domain objects, and returns the results in a hash map keyed by block ID.