How to access the database in Rust code across different contexts.
Core Types
| Type | Location | Use Case |
|---|---|---|
DbPool |
systemprompt::database::DbPool |
MCP servers, jobs |
Database |
systemprompt::database::Database |
Page data providers, extensions |
Arc<PgPool> |
sqlx::PgPool |
Repository constructors |
Pattern 1: MCP Server Handlers
MCP handlers receive &DbPool. Extract the underlying Arc<PgPool> with .pool().
use systemprompt::database::DbPool;
use systemprompt_web_extension::ContentRepository;
pub async fn handle(
db_pool: &DbPool,
// ... other params
) -> Result<CallToolResult, McpError> {
// Extract Arc<PgPool> from DbPool
let pg_pool = db_pool.pool().ok_or_else(|| {
McpError::internal_error("Database pool not available", None)
})?;
// Create repository with the pool
let content_repo = ContentRepository::new(pg_pool);
// Use repository
let content = content_repo.create(¶ms).await.map_err(|e| {
McpError::internal_error(format!("Database error: {e}"), None)
})?;
Ok(result)
}
Key points:
DbPool.pool()returnsOption<Arc<PgPool>>- Always handle the
Nonecase (pool not initialized) - Repositories take
Arc<PgPool>in their constructors
Pattern 2: Job Handlers
Jobs access the pool via JobContext.db_pool().
use systemprompt::database::DbPool;
use systemprompt::traits::{Job, JobContext, JobResult};
#[async_trait::async_trait]
impl Job for MyJob {
async fn execute(&self, ctx: &JobContext) -> Result<JobResult> {
let db_pool = ctx
.db_pool::<DbPool>()
.ok_or_else(|| anyhow::anyhow!("Database not available"))?;
// For direct sqlx queries, clone the pool
let result = sqlx::query("SELECT COUNT(*) FROM users")
.fetch_one(&**db_pool.pool().unwrap())
.await?;
// Or create a repository
let repo = ContentRepository::new(db_pool.pool().unwrap());
Ok(JobResult::success())
}
}
Pattern 3: Page Data Providers
Page providers access Arc<Database> from PageContext.
use std::sync::Arc;
use systemprompt::database::Database;
use systemprompt::template_provider::{PageContext, PageDataProvider};
#[async_trait]
impl PageDataProvider for MyProvider {
async fn provide_page_data(&self, ctx: &PageContext<'_>) -> Result<Value> {
// Get Database wrapper
let Some(db) = ctx.db_pool::<Arc<Database>>() else {
return Ok(json!({ "data": "" }));
};
// Extract PgPool
let Some(pool) = db.pool() else {
return Ok(json!({ "data": "" }));
};
// Use sqlx directly
let rows = sqlx::query_as!(
MyRow,
"SELECT * FROM my_table WHERE active = true"
)
.fetch_all(&*pool)
.await?;
Ok(json!({ "data": rows }))
}
}
Creating Repositories
Repositories wrap database access with typed methods.
Repository Pattern
use sqlx::PgPool;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct MyRepository {
pool: Arc<PgPool>,
}
impl MyRepository {
#[must_use]
pub fn new(pool: Arc<PgPool>) -> Self {
Self { pool }
}
pub async fn create(&self, params: &CreateParams) -> Result<MyModel, sqlx::Error> {
sqlx::query_as!(
MyModel,
r#"
INSERT INTO my_table (id, name, created_at)
VALUES ($1, $2, NOW())
RETURNING id, name, created_at
"#,
params.id,
params.name
)
.fetch_one(&*self.pool)
.await
}
pub async fn get_by_id(&self, id: &str) -> Result<Option<MyModel>, sqlx::Error> {
sqlx::query_as!(
MyModel,
"SELECT * FROM my_table WHERE id = $1",
id
)
.fetch_optional(&*self.pool)
.await
}
}
Using Extension Repositories
Extensions export their repositories. Add the extension as a dependency.
Cargo.toml
[dependencies]
systemprompt-web-extension = { path = "../../web" }
systemprompt-soul-extension = { path = "../../soul" }
Usage
use systemprompt_web_extension::{ContentRepository, CreateContentParams, ContentKind};
use systemprompt_soul_extension::{MemoryService, CreateMemoryParams};
// Content repository (web extension)
let content_repo = ContentRepository::new(pg_pool.clone());
let content = content_repo.create(&CreateContentParams::new(
slug,
title,
description,
body,
author,
Utc::now(),
SourceId::new("blog".to_string()),
)).await?;
// Memory service (soul extension)
let memory_service = MemoryService::new(pg_pool);
let memory = memory_service.store(&CreateMemoryParams::new(
memory_type,
category,
subject,
content,
)).await?;
Common Errors
| Error | Cause | Solution |
|---|---|---|
expected &Arc<Pool<Postgres>>, found &Arc<Database> |
Wrong type passed to repository | Use db_pool.pool() to extract Arc<PgPool> |
Database pool not available |
Pool not initialized | Check service startup, ensure DB connection |
Pool not initialized |
Lazy init not triggered | Database may not be connected yet |
Quick Reference
| Context | Get Pool | Type |
|---|---|---|
| MCP handler | db_pool.pool()? |
Option<Arc<PgPool>> |
| Job | ctx.db_pool::<DbPool>()?.pool()? |
Option<Arc<PgPool>> |
| Page provider | ctx.db_pool::<Arc<Database>>()?.pool()? |
Option<Arc<PgPool>> |
| Repository constructor | Repository::new(pool) |
Arc<PgPool> |