resolver.rs
Overview
This file defines the MessageLoader struct and its implementation as a data loader for asynchronously fetching Message entities from a SQLite database. The loader facilitates batching and caching of database queries to efficiently resolve GraphQL requests involving messages by their IDs. It leverages the async-graphql crate's Loader trait and uses SQLx for database interaction.
Components
MessageLoader Struct
pub struct MessageLoader {
pub pool: SqlitePool,
}
Purpose: Encapsulates a connection pool to a SQLite database (
SqlitePool) that is used to execute queries to load messages.Fields:
pool: An instance ofSqlitePoolrepresenting a pool of connections to the SQLite database.
impl Loader<String> for MessageLoader
This implementation makes MessageLoader conform to the Loader trait from async_graphql::dataloader. This enables it to be used as a data loader in GraphQL resolvers, providing efficient batching and caching mechanisms.
Associated Types
type Error = Error: Specifies that the error type returned by loader methods isasync_graphql::Error.type Value = super::Message: The type of values loaded by this loader, which is aMessagestruct defined in the parent module (refer toMessage Entityfor the full definition).
Method: load
async fn load(
&self,
keys: &[String],
) -> anyhow::Result<HashMap<String, Self::Value>, Self::Error>
Purpose: Given a slice of message ID strings (
keys), asynchronously fetches the correspondingMessagerecords from the database and returns them in a hash map keyed by message ID.Parameters:
keys: &[String]— A slice of message ID strings that need to be loaded.
Returns:
An
anyhow::Resultwrapping aHashMap<String, Message>mapping each message ID to its correspondingMessageinstance.
Usage:
Typically called internally by the GraphQL execution engine when resolving fields that require message data.
Supports efficient batch loading by querying multiple message IDs in a single SQL statement.
Implementation Details:
The method formats the message IDs into a comma-separated string to use in a SQL
INclause.Constructs a raw SQL query string:
SELECT * FROM messages WHERE id IN (<comma_separated_ids>)Uses
sqlx::QueryBuilderto build and execute the query against the SQLite connection pool.Maps each returned database record (
db::Message) into the application'sMessagetype.Collects the results into a
HashMapkeyed by message ID.If any error occurs during database access or mapping, it propagates the error as an
async_graphql::Error.
Tracing:
Logs the generated SQL query at trace level with target
"data_loader"for debugging purposes.
Example Usage
let loader = MessageLoader { pool: sqlite_pool.clone() };
let message_ids = vec!["msg1".to_string(), "msg2".to_string()];
let messages_map = loader.load(&message_ids).await?;
if let Some(message) = messages_map.get("msg1") {
// Use the message instance
}
Important Implementation Notes
The loader uses a raw SQL query constructed via string interpolation for the
INclause, which may raise concerns about SQL injection if keys are not properly sanitized. However, since the keys are expected to be message IDs generated internally, the risk is mitigated.The loader performs batching by accepting multiple keys and querying them all at once, which reduces the number of database roundtrips compared to loading messages individually.
The use of
futures::TryStreamExtand SQLx's asynchronous fetching allows non-blocking database operations, improving scalability in asynchronous environments.Conversion from the database message model (
db::Message) to the application model (super::Message) is done via theFromtrait or equivalent implementation (msg.into()), ensuring separation between database and domain models.
Interaction with Other Components
Relies on the
SqlitePoolprovided by the database connection management layer (crate::schema::db).The
super::Messagetype represents the domain model for messages and is expected to provide conversions fromdb::Message.Intended to be used as a data loader in GraphQL resolvers, enabling efficient resolution of fields requiring message data by ID. This integration aligns with the
async_graphqlframework's data loader pattern.The
MessageLoaderis part of the data access layer for message entities and interacts indirectly with other modules responsible for schema definition and resolver logic.
Mermaid Diagram: Class Structure of MessageLoader
classDiagram
class MessageLoader {
+pool: SqlitePool
+load(keys: &[String]) async -> Result<HashMap<String, Message>, Error>
}
MessageLoader ..|> Loader
This diagram shows the MessageLoader struct containing a pool field and implementing the asynchronous load method as part of the Loader trait specialization for String keys and Message values.