1extern crate self as hydracache_sqlx;
91
92mod error;
93mod query_ext;
94
95pub use error::{Result, SqlxCacheError};
96pub use hydracache_db::{
97 prepared_query_policy, query_cache_policy, CacheEntity, CacheKeyBuilder, DbAdapterKind,
98 DbCache, DbCacheError, DbOperationContext, DbQuery, DbResultShape, HydraCacheEntity,
99 InvalidationPlan, InvalidationReport, PreparedDbQuery, PreparedQueryPolicy, QueryCachePolicy,
100 RefreshPolicy, Result as DbResult,
101};
102pub use query_ext::SqlxQueryExt;
103
104pub type SqlxCache<C = hydracache::PostcardCodec> = DbCache<C>;
106
107pub type SqlxQuery<T, C = hydracache::PostcardCodec> = DbQuery<T, C>;
109
110pub use sqlx;
115
116#[cfg(test)]
117mod tests {
118 use hydracache::HydraCache;
119 use serde::{Deserialize, Serialize};
120 use sqlx::postgres::PgPoolOptions;
121
122 use crate::{
123 prepared_query_policy, query_cache_policy, CacheKeyBuilder, DbCache, InvalidationPlan,
124 PreparedQueryPolicy, QueryCachePolicy, RefreshPolicy, SqlxCache, SqlxQueryExt,
125 };
126
127 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128 struct User {
129 id: u64,
130 }
131
132 #[tokio::test]
133 async fn sqlx_cache_alias_matches_database_cache_api() {
134 let query = SqlxCache::new(HydraCache::local().build(), "sqlx")
135 .cached::<User>()
136 .key("user:1");
137
138 assert_eq!(query.physical_key(), Some("sqlx:user:1".to_owned()));
139 }
140
141 #[tokio::test]
142 async fn db_cache_reexport_is_available_from_sqlx_crate() {
143 let query = DbCache::new(HydraCache::local().build(), "db")
144 .cached::<User>()
145 .key("user:1");
146
147 assert_eq!(query.physical_key(), Some("db:user:1".to_owned()));
148
149 let pending = InvalidationPlan::new().tag("user:1");
150 assert_eq!(pending.tag_values().collect::<Vec<_>>(), vec!["user:1"]);
151 }
152
153 #[tokio::test]
154 async fn query_cache_policy_reexport_is_available_from_sqlx_crate() {
155 let refresh =
156 RefreshPolicy::new().stale_while_revalidate(std::time::Duration::from_secs(5));
157 let policy = QueryCachePolicy::new()
158 .key("user:1")
159 .tag("user:1")
160 .refresh_policy(refresh);
161 let query = DbCache::new(HydraCache::local().build(), "db").cached_with::<User>(policy);
162
163 assert_eq!(query.physical_key(), Some("db:user:1".to_owned()));
164 assert_eq!(query.tags_value(), &["user:1".to_owned()]);
165 assert_eq!(query.refresh_policy_value(), Some(refresh));
166 }
167
168 #[test]
169 fn query_policy_segment_macro_reexport_uses_sqlx_crate_paths() {
170 let policy = query_cache_policy!(
171 name = "search-users",
172 key_segments = ["tenant", 7_u64, "q", "ada:lovelace"],
173 tag_segments = [["tenant", 7_u64], ["users"]],
174 ttl_secs = 30,
175 );
176 let expected_key = CacheKeyBuilder::new()
177 .segment("tenant")
178 .segment(7_u64)
179 .segment("q")
180 .segment("ada:lovelace")
181 .build_string();
182
183 assert_eq!(policy.name(), Some("search-users"));
184 assert_eq!(policy.key_value(), Some(expected_key.as_str()));
185 assert_eq!(
186 policy.tags_value(),
187 &["tenant:7".to_owned(), "users".to_owned()]
188 );
189 }
190
191 #[tokio::test]
192 async fn prepared_query_policy_reexport_is_available_from_sqlx_crate() {
193 let policy = prepared_query_policy!(
194 entity = "user",
195 name = "load-user",
196 collection_tag = "users",
197 );
198 let expected = PreparedQueryPolicy::for_entity("user")
199 .with_name("load-user")
200 .collection_tag("users");
201 assert_eq!(policy, expected);
202
203 let prepared = DbCache::new(HydraCache::local().build(), "db").prepare::<User>(policy);
204
205 let query = prepared.for_id(1);
206 assert_eq!(query.name(), Some("load-user"));
207 assert_eq!(query.physical_key(), Some("db:user:1".to_owned()));
208 assert_eq!(
209 query.tags_value(),
210 &["users".to_owned(), "user:1".to_owned()]
211 );
212 }
213
214 #[tokio::test]
215 async fn sqlx_helper_missing_key_returns_sqlx_cache_error() {
216 let pool = PgPoolOptions::new()
217 .connect_lazy("postgres://postgres:postgres@localhost/postgres")
218 .unwrap();
219
220 let result = DbCache::new(HydraCache::local().build(), "db")
221 .cached::<(i64,)>()
222 .sqlx_one(pool, sqlx::query_as("select 1"))
223 .await;
224
225 let error = result.unwrap_err();
226 assert_eq!(
227 error.to_string(),
228 "database cached operation `db:unnamed` is missing an explicit cache key (adapter=sqlx, namespace=db, result_shape=one)"
229 );
230 }
231
232 #[tokio::test]
233 async fn sqlx_cache_error_wraps_db_cache_errors() {
234 let error = hydracache_db::DbCacheError::MissingKey {
235 operation: "load-user".to_owned(),
236 adapter: hydracache_db::DbAdapterKind::Sqlx,
237 namespace: "db".to_owned(),
238 result_shape: hydracache_db::DbResultShape::One,
239 };
240 let error = crate::SqlxCacheError::from(error);
241
242 assert_eq!(
243 error.to_string(),
244 "database cached operation `load-user` is missing an explicit cache key (adapter=sqlx, namespace=db, result_shape=one)"
245 );
246 }
247}