extern crate self as hydracache_sqlx;
mod error;
mod query_ext;
mod transaction;
pub use error::{Result, SqlxCacheError, SqlxTransactionError, TransactionResult};
pub use hydracache_db::{
prepared_query_policy, query_cache_policy, CacheEntity, CacheKeyBuilder, DbAdapterKind,
DbCache, DbCacheError, DbOperationContext, DbQuery, DbResultShape, HydraCacheEntity,
InvalidationCollector, InvalidationPlan, InvalidationReport, PgNotifyIntent,
PgNotifyIntentSource, PreparedDbQuery, PreparedQueryPolicy, QueryCachePolicy, RefreshPolicy,
Result as DbResult, SqlxInvalidationOutbox, OUTBOX_SCHEMA_VERSION,
};
pub use query_ext::SqlxQueryExt;
pub use transaction::{
SqlxTransactionCompanion, SqlxTransactionDiagnostics, SqlxTransactionExt,
SqlxTransactionFuture, SqlxTransactionReport,
};
pub type SqlxCache<C = hydracache::PostcardCodec> = DbCache<C>;
pub type SqlxQuery<T, C = hydracache::PostcardCodec> = DbQuery<T, C>;
pub use sqlx;
#[cfg(test)]
mod tests {
use hydracache::HydraCache;
use serde::{Deserialize, Serialize};
use sqlx::postgres::PgPoolOptions;
use crate::{
prepared_query_policy, query_cache_policy, CacheKeyBuilder, DbCache, InvalidationPlan,
PreparedQueryPolicy, QueryCachePolicy, RefreshPolicy, SqlxCache, SqlxQueryExt,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
struct User {
id: u64,
}
#[tokio::test]
async fn sqlx_cache_alias_matches_database_cache_api() {
let query = SqlxCache::new(HydraCache::local().build(), "sqlx")
.cached::<User>()
.key("user:1");
assert_eq!(query.physical_key(), Some("sqlx:user:1".to_owned()));
}
#[tokio::test]
async fn db_cache_reexport_is_available_from_sqlx_crate() {
let query = DbCache::new(HydraCache::local().build(), "db")
.cached::<User>()
.key("user:1");
assert_eq!(query.physical_key(), Some("db:user:1".to_owned()));
let pending = InvalidationPlan::new().tag("user:1");
assert_eq!(pending.tag_values().collect::<Vec<_>>(), vec!["user:1"]);
}
#[tokio::test]
async fn query_cache_policy_reexport_is_available_from_sqlx_crate() {
let refresh =
RefreshPolicy::new().stale_while_revalidate(std::time::Duration::from_secs(5));
let policy = QueryCachePolicy::new()
.key("user:1")
.tag("user:1")
.refresh_policy(refresh);
let query = DbCache::new(HydraCache::local().build(), "db").cached_with::<User>(policy);
assert_eq!(query.physical_key(), Some("db:user:1".to_owned()));
assert_eq!(query.tags_value(), &["user:1".to_owned()]);
assert_eq!(query.refresh_policy_value(), Some(refresh));
}
#[test]
fn query_policy_segment_macro_reexport_uses_sqlx_crate_paths() {
let policy = query_cache_policy!(
name = "search-users",
key_segments = ["tenant", 7_u64, "q", "ada:lovelace"],
tag_segments = [["tenant", 7_u64], ["users"]],
ttl_secs = 30,
);
let expected_key = CacheKeyBuilder::new()
.segment("tenant")
.segment(7_u64)
.segment("q")
.segment("ada:lovelace")
.build_string();
assert_eq!(policy.name(), Some("search-users"));
assert_eq!(policy.key_value(), Some(expected_key.as_str()));
assert_eq!(
policy.tags_value(),
&["tenant:7".to_owned(), "users".to_owned()]
);
}
#[tokio::test]
async fn prepared_query_policy_reexport_is_available_from_sqlx_crate() {
let policy = prepared_query_policy!(
entity = "user",
name = "load-user",
collection_tag = "users",
);
let expected = PreparedQueryPolicy::for_entity("user")
.with_name("load-user")
.collection_tag("users");
assert_eq!(policy, expected);
let prepared = DbCache::new(HydraCache::local().build(), "db").prepare::<User>(policy);
let query = prepared.for_id(1);
assert_eq!(query.name(), Some("load-user"));
assert_eq!(query.physical_key(), Some("db:user:1".to_owned()));
assert_eq!(
query.tags_value(),
&["users".to_owned(), "user:1".to_owned()]
);
}
#[tokio::test]
async fn sqlx_helper_missing_key_returns_sqlx_cache_error() {
let pool = PgPoolOptions::new()
.connect_lazy("postgres://postgres:postgres@localhost/postgres")
.unwrap();
let result = DbCache::new(HydraCache::local().build(), "db")
.cached::<(i64,)>()
.sqlx_one(pool, sqlx::query_as("select 1"))
.await;
let error = result.unwrap_err();
assert_eq!(
error.to_string(),
"database cached operation `db:unnamed` is missing an explicit cache key (adapter=sqlx, namespace=db, result_shape=one)"
);
}
#[tokio::test]
async fn sqlx_cache_error_wraps_db_cache_errors() {
let error = hydracache_db::DbCacheError::MissingKey {
operation: "load-user".to_owned(),
adapter: hydracache_db::DbAdapterKind::Sqlx,
namespace: "db".to_owned(),
result_shape: hydracache_db::DbResultShape::One,
};
let error = crate::SqlxCacheError::from(error);
assert_eq!(
error.to_string(),
"database cached operation `load-user` is missing an explicit cache key (adapter=sqlx, namespace=db, result_shape=one)"
);
}
}