Skip to main content

cdk_sql_common/mint/
mod.rs

1//! SQL database implementation of the Mint
2//!
3//! This is a generic SQL implementation for the mint storage layer. Any database can be plugged in
4//! as long as standard ANSI SQL is used, as Postgres and SQLite would understand it.
5//!
6//! This implementation also has a rudimentary but standard migration and versioning system.
7//!
8//! The trait expects an asynchronous interaction, but it also provides tools to spawn blocking
9//! clients in a pool and expose them to an asynchronous environment, making them compatible with
10//! Mint.
11use std::fmt::Debug;
12use std::sync::Arc;
13
14use async_trait::async_trait;
15use cdk_common::database::{self, DbTransactionFinalizer, Error, MintDatabase};
16
17use crate::common::migrate;
18use crate::database::{ConnectionWithTransaction, DatabaseExecutor};
19use crate::pool::{DatabasePool, Pool, PooledResource};
20
21mod auth;
22mod completed_operations;
23mod keys;
24mod keyvalue;
25mod proofs;
26mod quotes;
27mod saga;
28mod signatures;
29
30#[rustfmt::skip]
31mod migrations {
32    include!(concat!(env!("OUT_DIR"), "/migrations_mint.rs"));
33}
34
35pub use auth::SQLMintAuthDatabase;
36#[cfg(feature = "prometheus")]
37use cdk_prometheus::METRICS;
38use migrations::MIGRATIONS;
39
40/// Mint SQL Database
41#[derive(Debug, Clone)]
42pub struct SQLMintDatabase<RM>
43where
44    RM: DatabasePool + 'static,
45{
46    pub(crate) pool: Arc<Pool<RM>>,
47}
48
49/// SQL Transaction Writer
50#[allow(missing_debug_implementations)]
51pub struct SQLTransaction<RM>
52where
53    RM: DatabasePool + 'static,
54{
55    pub(crate) inner: ConnectionWithTransaction<RM::Connection, PooledResource<RM>>,
56}
57
58impl<RM> SQLMintDatabase<RM>
59where
60    RM: DatabasePool + 'static,
61{
62    /// Creates a new instance
63    pub async fn new<X>(db: X) -> Result<Self, Error>
64    where
65        X: Into<RM::Config>,
66    {
67        let pool = Pool::new(db.into());
68
69        Self::migrate(pool.get().map_err(|e| Error::Database(Box::new(e)))?).await?;
70
71        Ok(Self { pool })
72    }
73
74    /// Migrate
75    async fn migrate(conn: PooledResource<RM>) -> Result<(), Error> {
76        let tx = ConnectionWithTransaction::new(conn).await?;
77        migrate(&tx, RM::Connection::name(), MIGRATIONS).await?;
78        tx.commit().await?;
79        Ok(())
80    }
81}
82
83#[async_trait]
84impl<RM> database::MintTransaction<Error> for SQLTransaction<RM> where RM: DatabasePool + 'static {}
85
86#[async_trait]
87impl<RM> DbTransactionFinalizer for SQLTransaction<RM>
88where
89    RM: DatabasePool + 'static,
90{
91    type Err = Error;
92
93    async fn commit(self: Box<Self>) -> Result<(), Error> {
94        let result = self.inner.commit().await;
95        #[cfg(feature = "prometheus")]
96        {
97            let success = result.is_ok();
98            METRICS.record_mint_operation("transaction_commit", success);
99            METRICS.record_mint_operation_histogram("transaction_commit", success, 1.0);
100        }
101
102        Ok(result?)
103    }
104
105    async fn rollback(self: Box<Self>) -> Result<(), Error> {
106        let result = self.inner.rollback().await;
107
108        #[cfg(feature = "prometheus")]
109        {
110            let success = result.is_ok();
111            METRICS.record_mint_operation("transaction_rollback", success);
112            METRICS.record_mint_operation_histogram("transaction_rollback", success, 1.0);
113        }
114        Ok(result?)
115    }
116}
117
118#[async_trait]
119impl<RM> MintDatabase<Error> for SQLMintDatabase<RM>
120where
121    RM: DatabasePool + 'static,
122{
123    async fn begin_transaction(
124        &self,
125    ) -> Result<Box<dyn database::MintTransaction<Error> + Send + Sync>, Error> {
126        let tx = SQLTransaction {
127            inner: ConnectionWithTransaction::new(
128                self.pool.get().map_err(|e| Error::Database(Box::new(e)))?,
129            )
130            .await?,
131        };
132
133        Ok(Box::new(tx))
134    }
135}