Skip to main content

hydracache_sqlx/
lib.rs

1//! SQLx-facing integration crate for HydraCache database result caching.
2//!
3//! The database-neutral query cache API lives in `hydracache-db`. This crate
4//! keeps SQLx users on a convenient import path while avoiding a hard conceptual
5//! dependency between the generic adapter and SQLx itself.
6//!
7//! # Example
8//!
9//! ```no_run
10//! use hydracache::HydraCache;
11//! use hydracache_sqlx::{DbCache, HydraCacheEntity, SqlxQueryExt};
12//!
13//! #[derive(serde::Serialize, serde::Deserialize, HydraCacheEntity)]
14//! #[hydracache(entity = "user", collection = "users", id = i64)]
15//! struct User {
16//!     id: i64,
17//!     name: String,
18//! }
19//!
20//! # async fn example(pool: sqlx::PgPool) -> hydracache_sqlx::Result<()> {
21//! let local = HydraCache::local().build();
22//!
23//! // SQLx users may import DbCache from this crate, but the type itself is
24//! // database-neutral and comes from hydracache-db.
25//! let queries = DbCache::new(local, "db");
26//!
27//! let user: User = queries
28//!     .for_entity::<User>(42)
29//!     .fetch_with(move || async move {
30//!         let (id, name): (i64, String) =
31//!             sqlx::query_as("select id, name from users where id = $1")
32//!                 .bind(42_i64)
33//!                 .fetch_one(&pool)
34//!                 .await?;
35//!
36//!         Ok::<_, sqlx::Error>(User { id, name })
37//!     })
38//!     .await?;
39//!
40//! assert_eq!(user.id, 42);
41//! assert!(!user.name.is_empty());
42//! # Ok(())
43//! # }
44//! ```
45//!
46//! Use [`DbQuery::fetch_with`] when you need SQLx macros, transactions, or a
47//! repository function instead of a pool-like executor.
48
49extern crate self as hydracache_sqlx;
50
51mod error;
52mod query_ext;
53
54pub use error::{Result, SqlxCacheError};
55pub use hydracache_db::{
56    CacheEntity, DbCache, DbCacheError, DbQuery, HydraCacheEntity, Result as DbResult,
57};
58pub use query_ext::SqlxQueryExt;
59
60/// SQLx-specific compatibility name for [`DbCache`].
61pub type SqlxCache<C = hydracache::PostcardCodec> = DbCache<C>;
62
63/// SQLx-specific compatibility name for [`DbQuery`].
64pub type SqlxQuery<T, C = hydracache::PostcardCodec> = DbQuery<T, C>;
65
66/// Re-export the SQLx crate used by this adapter.
67///
68/// This lets downstream users keep one adapter-aligned SQLx version in examples
69/// and integration code without hiding SQLx behind HydraCache abstractions.
70pub use sqlx;
71
72#[cfg(test)]
73mod tests {
74    use hydracache::HydraCache;
75    use serde::{Deserialize, Serialize};
76    use sqlx::postgres::PgPoolOptions;
77
78    use crate::{DbCache, SqlxCache, SqlxQueryExt};
79
80    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81    struct User {
82        id: u64,
83    }
84
85    #[tokio::test]
86    async fn sqlx_cache_alias_matches_database_cache_api() {
87        let query = SqlxCache::new(HydraCache::local().build(), "sqlx")
88            .cached::<User>()
89            .key("user:1");
90
91        assert_eq!(query.physical_key(), Some("sqlx:user:1".to_owned()));
92    }
93
94    #[tokio::test]
95    async fn db_cache_reexport_is_available_from_sqlx_crate() {
96        let query = DbCache::new(HydraCache::local().build(), "db")
97            .cached::<User>()
98            .key("user:1");
99
100        assert_eq!(query.physical_key(), Some("db:user:1".to_owned()));
101    }
102
103    #[tokio::test]
104    async fn sqlx_helper_missing_key_returns_sqlx_cache_error() {
105        let pool = PgPoolOptions::new()
106            .connect_lazy("postgres://postgres:postgres@localhost/postgres")
107            .unwrap();
108
109        let result = DbCache::new(HydraCache::local().build(), "db")
110            .cached::<(i64,)>()
111            .fetch_one(pool, sqlx::query_as("select 1"))
112            .await;
113
114        let error = result.unwrap_err();
115        assert_eq!(
116            error.to_string(),
117            "database cached operation `db:unnamed` is missing an explicit cache key"
118        );
119    }
120
121    #[tokio::test]
122    async fn sqlx_cache_error_wraps_db_cache_errors() {
123        let error = hydracache_db::DbCacheError::MissingKey {
124            operation: "load-user".to_owned(),
125        };
126        let error = crate::SqlxCacheError::from(error);
127
128        assert_eq!(
129            error.to_string(),
130            "database cached operation `load-user` is missing an explicit cache key"
131        );
132    }
133}