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