hopr_db_sql/
lib.rs

1//! Crate for accessing database(s) of a HOPR node.
2//! Functionality defined here is meant to be used mostly by other higher-level crates.
3
4pub mod accounts;
5mod cache;
6pub mod channels;
7pub mod db;
8pub mod errors;
9pub mod info;
10pub mod logs;
11pub mod peers;
12pub mod protocol;
13pub mod registry;
14pub mod resolver;
15mod ticket_manager;
16pub mod tickets;
17
18pub use hopr_db_api as api;
19
20pub use sea_orm::DatabaseConnection;
21pub use sea_orm::DatabaseTransaction;
22
23use crate::accounts::HoprDbAccountOperations;
24use crate::channels::HoprDbChannelOperations;
25use async_trait::async_trait;
26use futures::future::BoxFuture;
27use sea_orm::TransactionTrait;
28
29use crate::db::HoprDb;
30use crate::errors::{DbSqlError, Result};
31use crate::info::HoprDbInfoOperations;
32use crate::registry::HoprDbRegistryOperations;
33use hopr_db_api::logs::HoprDbLogOperations;
34use hopr_db_api::peers::HoprDbPeersOperations;
35use hopr_db_api::protocol::HoprDbProtocolOperations;
36use hopr_db_api::resolver::HoprDbResolverOperations;
37use hopr_db_api::tickets::HoprDbTicketOperations;
38
39/// Primary key used in tables that contain only a single row.
40pub const SINGULAR_TABLE_FIXED_ID: i32 = 1;
41
42/// Shorthand for the `chrono` based timestamp type used in the database.
43pub type DbTimestamp = chrono::DateTime<chrono::Utc>;
44
45/// Represents an already opened transaction.
46/// This is a thin wrapper over [DatabaseTransaction].
47/// The wrapping behavior is needed to allow transaction agnostic functionalities
48/// of the DB traits.
49#[derive(Debug)]
50pub struct OpenTransaction(DatabaseTransaction, TargetDb);
51
52impl OpenTransaction {
53    /// Executes the given `callback` inside the transaction
54    /// and commits the transaction if it succeeds or rollbacks otherwise.
55    pub async fn perform<F, T, E>(self, callback: F) -> std::result::Result<T, E>
56    where
57        F: for<'c> FnOnce(&'c OpenTransaction) -> BoxFuture<'c, std::result::Result<T, E>> + Send,
58        T: Send,
59        E: std::error::Error + From<DbSqlError>,
60    {
61        let res = callback(&self).await;
62
63        if res.is_ok() {
64            self.commit().await?;
65        } else {
66            self.rollback().await?;
67        }
68        res
69    }
70
71    /// Commits the transaction.
72    pub async fn commit(self) -> Result<()> {
73        Ok(self.0.commit().await?)
74    }
75
76    /// Rollbacks the transaction.
77    pub async fn rollback(self) -> Result<()> {
78        Ok(self.0.rollback().await?)
79    }
80}
81
82impl AsRef<DatabaseTransaction> for OpenTransaction {
83    fn as_ref(&self) -> &DatabaseTransaction {
84        &self.0
85    }
86}
87
88impl From<OpenTransaction> for DatabaseTransaction {
89    fn from(value: OpenTransaction) -> Self {
90        value.0
91    }
92}
93
94/// Shorthand for optional transaction.
95/// Useful for transaction nesting (see [`HoprDbGeneralModelOperations::nest_transaction`]).
96pub type OptTx<'a> = Option<&'a OpenTransaction>;
97
98/// When Sqlite is used as a backend, model needs to be split
99/// into 4 different databases to avoid locking the database.
100/// On Postgres backend, these should actually point to the same database.
101#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
102pub enum TargetDb {
103    #[default]
104    /// Indexer database.
105    Index,
106    /// Acknowledged winning ticket database.
107    Tickets,
108    /// Network peers database
109    Peers,
110    /// RPC logs database
111    Logs,
112}
113
114#[async_trait]
115pub trait HoprDbGeneralModelOperations {
116    /// Returns reference to the database connection.
117    /// Can be used in case transaction is not needed, but
118    /// users should aim to use [`HoprDbGeneralModelOperations::begin_transaction`]
119    /// and [`HoprDbGeneralModelOperations::nest_transaction`] as much as possible.
120    fn conn(&self, target_db: TargetDb) -> &DatabaseConnection;
121
122    /// Creates a new transaction.
123    async fn begin_transaction_in_db(&self, target: TargetDb) -> Result<OpenTransaction>;
124
125    /// Same as [`HoprDbGeneralModelOperations::begin_transaction_in_db`] with default [TargetDb].
126    async fn begin_transaction(&self) -> Result<OpenTransaction> {
127        self.begin_transaction_in_db(Default::default()).await
128    }
129
130    /// Creates a nested transaction inside the given transaction.
131    ///
132    /// If `None` is given, behaves exactly as [`HoprDbGeneralModelOperations::begin_transaction`].
133    ///
134    /// This method is useful for creating APIs that should be agnostic whether they are being
135    /// run from an existing transaction or without it (via [OptTx]).
136    ///
137    /// If `tx` is `Some`, the `target_db` must match with the one in `tx`. In other words,
138    /// nesting across different databases is forbidden and the method will panic.
139    async fn nest_transaction_in_db(&self, tx: OptTx<'_>, target_db: TargetDb) -> Result<OpenTransaction> {
140        if let Some(t) = tx {
141            assert_eq!(t.1, target_db, "attempt to create nest into tx from a different db");
142            Ok(OpenTransaction(t.as_ref().begin().await?, target_db))
143        } else {
144            self.begin_transaction_in_db(target_db).await
145        }
146    }
147
148    /// Same as [`HoprDbGeneralModelOperations::nest_transaction_in_db`] with default [TargetDb].
149    async fn nest_transaction(&self, tx: OptTx<'_>) -> Result<OpenTransaction> {
150        self.nest_transaction_in_db(tx, Default::default()).await
151    }
152}
153
154#[async_trait]
155impl HoprDbGeneralModelOperations for HoprDb {
156    /// Retrieves raw database connection to the given [DB](TargetDb).
157    fn conn(&self, target_db: TargetDb) -> &DatabaseConnection {
158        match target_db {
159            TargetDb::Index => &self.index_db,
160            TargetDb::Tickets => &self.tickets_db,
161            TargetDb::Peers => &self.peers_db,
162            TargetDb::Logs => &self.logs_db,
163        }
164    }
165
166    /// Starts a new transaction in the given [DB](TargetDb).
167    async fn begin_transaction_in_db(&self, target_db: TargetDb) -> Result<OpenTransaction> {
168        match target_db {
169            TargetDb::Index => Ok(OpenTransaction(
170                self.index_db.begin_with_config(None, None).await?,
171                target_db,
172            )),
173            // TODO: when adding Postgres support, redirect `Tickets` and `Peers` into `self.db`
174            TargetDb::Tickets => Ok(OpenTransaction(
175                self.tickets_db.begin_with_config(None, None).await?,
176                target_db,
177            )),
178            TargetDb::Peers => Ok(OpenTransaction(
179                self.peers_db.begin_with_config(None, None).await?,
180                target_db,
181            )),
182            TargetDb::Logs => Ok(OpenTransaction(
183                self.logs_db.begin_with_config(None, None).await?,
184                target_db,
185            )),
186        }
187    }
188}
189
190/// Convenience trait that contain all HOPR DB operations crates.
191pub trait HoprDbAllOperations:
192    HoprDbGeneralModelOperations
193    + HoprDbAccountOperations
194    + HoprDbChannelOperations
195    + HoprDbInfoOperations
196    + HoprDbRegistryOperations
197    + HoprDbTicketOperations
198    + HoprDbPeersOperations
199    + HoprDbResolverOperations
200    + HoprDbProtocolOperations
201    + HoprDbLogOperations
202{
203}
204
205#[doc(hidden)]
206pub mod prelude {
207    pub use super::*;
208    pub use crate::accounts::*;
209    pub use crate::channels::*;
210    pub use crate::db::*;
211    pub use crate::errors::*;
212    pub use crate::info::*;
213    pub use crate::registry::*;
214    pub use hopr_db_api::logs::*;
215    pub use hopr_db_api::peers::*;
216    pub use hopr_db_api::protocol::*;
217    pub use hopr_db_api::resolver::*;
218    pub use hopr_db_api::tickets::*;
219}