//! SQL database implementation of the Mint //! //! This is a generic SQL implementation for the mint storage layer. Any database can be plugged in //! as long as standard ANSI SQL is used, as Postgres and SQLite would understand it. //! //! This implementation also has a rudimentary but standard migration and versioning system. //! //! The trait expects an asynchronous interaction, but it also provides tools to spawn blocking //! clients in a pool and expose them to an asynchronous environment, making them compatible with //! Mint. use std::fmt::Debug; use std::sync::Arc; use async_trait::async_trait; use cdk_common::database::{self, DbTransactionFinalizer, Error, MintDatabase}; use crate::common::migrate; use crate::database::{ConnectionWithTransaction, DatabaseExecutor}; use crate::pool::{DatabasePool, Pool, PooledResource}; #[cfg(feature = "auth")] mod auth; mod completed_operations; mod keys; mod keyvalue; mod proofs; mod quotes; mod saga; mod signatures; #[rustfmt::skip] mod migrations { include!(concat!(env!("OUT_DIR"), "/migrations_mint.rs")); } #[cfg(feature = "auth")] pub use auth::SQLMintAuthDatabase; #[cfg(feature = "prometheus")] use cdk_prometheus::METRICS; use migrations::MIGRATIONS; /// Mint SQL Database #[derive(Debug, Clone)] pub struct SQLMintDatabase where RM: DatabasePool + 'static, { pub(crate) pool: Arc>, } /// SQL Transaction Writer #[allow(missing_debug_implementations)] pub struct SQLTransaction where RM: DatabasePool + 'static, { pub(crate) inner: ConnectionWithTransaction>, } impl SQLMintDatabase where RM: DatabasePool + 'static, { /// Creates a new instance pub async fn new(db: X) -> Result where X: Into, { let pool = Pool::new(db.into()); Self::migrate(pool.get().map_err(|e| Error::Database(Box::new(e)))?).await?; Ok(Self { pool }) } /// Migrate async fn migrate(conn: PooledResource) -> Result<(), Error> { let tx = ConnectionWithTransaction::new(conn).await?; migrate(&tx, RM::Connection::name(), MIGRATIONS).await?; tx.commit().await?; Ok(()) } } #[async_trait] impl database::MintTransaction for SQLTransaction where RM: DatabasePool + 'static {} #[async_trait] impl DbTransactionFinalizer for SQLTransaction where RM: DatabasePool + 'static, { type Err = Error; async fn commit(self: Box) -> Result<(), Error> { let result = self.inner.commit().await; #[cfg(feature = "prometheus")] { let success = result.is_ok(); METRICS.record_mint_operation("transaction_commit", success); METRICS.record_mint_operation_histogram("transaction_commit", success, 1.0); } Ok(result?) } async fn rollback(self: Box) -> Result<(), Error> { let result = self.inner.rollback().await; #[cfg(feature = "prometheus")] { let success = result.is_ok(); METRICS.record_mint_operation("transaction_rollback", success); METRICS.record_mint_operation_histogram("transaction_rollback", success, 1.0); } Ok(result?) } } #[async_trait] impl MintDatabase for SQLMintDatabase where RM: DatabasePool + 'static, { async fn begin_transaction( &self, ) -> Result + Send + Sync>, Error> { let tx = SQLTransaction { inner: ConnectionWithTransaction::new( self.pool.get().map_err(|e| Error::Database(Box::new(e)))?, ) .await?, }; Ok(Box::new(tx)) } }