|
| 1 | +use anyhow::Context; |
| 2 | +use sqlite::Value; |
| 3 | + |
| 4 | +use mithril_common::entities::BlockNumber; |
| 5 | +use mithril_common::StdResult; |
| 6 | + |
| 7 | +use crate::database::record::CardanoTransactionRecord; |
| 8 | +use crate::sqlite::{ |
| 9 | + EntityCursor, Provider, SourceAlias, SqLiteEntity, SqliteConnection, WhereCondition, |
| 10 | +}; |
| 11 | + |
| 12 | +/// Query to delete old [CardanoTransactionRecord] from the sqlite database |
| 13 | +pub struct DeleteCardanoTransactionProvider<'conn> { |
| 14 | + connection: &'conn SqliteConnection, |
| 15 | +} |
| 16 | + |
| 17 | +impl<'conn> Provider<'conn> for DeleteCardanoTransactionProvider<'conn> { |
| 18 | + type Entity = CardanoTransactionRecord; |
| 19 | + |
| 20 | + fn get_connection(&'conn self) -> &'conn SqliteConnection { |
| 21 | + self.connection |
| 22 | + } |
| 23 | + |
| 24 | + fn get_definition(&self, condition: &str) -> String { |
| 25 | + // it is important to alias the fields with the same name as the table |
| 26 | + // since the table cannot be aliased in a RETURNING statement in SQLite. |
| 27 | + let projection = Self::Entity::get_projection() |
| 28 | + .expand(SourceAlias::new(&[("{:cardano_tx:}", "cardano_tx")])); |
| 29 | + |
| 30 | + format!("delete from cardano_tx where {condition} returning {projection}") |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +impl<'conn> DeleteCardanoTransactionProvider<'conn> { |
| 35 | + /// Create a new instance |
| 36 | + pub fn new(connection: &'conn SqliteConnection) -> Self { |
| 37 | + Self { connection } |
| 38 | + } |
| 39 | + |
| 40 | + fn get_prune_condition( |
| 41 | + &self, |
| 42 | + block_number_threshold: BlockNumber, |
| 43 | + ) -> StdResult<WhereCondition> { |
| 44 | + let threshold = Value::Integer(block_number_threshold.try_into().with_context(|| { |
| 45 | + format!("Failed to convert threshold `{block_number_threshold}` to i64") |
| 46 | + })?); |
| 47 | + |
| 48 | + Ok(WhereCondition::new("block_number < ?*", vec![threshold])) |
| 49 | + } |
| 50 | + |
| 51 | + /// Prune the cardano transaction data below the given threshold. |
| 52 | + pub fn prune( |
| 53 | + &self, |
| 54 | + block_number_threshold: BlockNumber, |
| 55 | + ) -> StdResult<EntityCursor<CardanoTransactionRecord>> { |
| 56 | + let filters = self.get_prune_condition(block_number_threshold)?; |
| 57 | + |
| 58 | + self.find(filters) |
| 59 | + } |
| 60 | +} |
| 61 | + |
| 62 | +#[cfg(test)] |
| 63 | +mod tests { |
| 64 | + use crate::database::provider::{ |
| 65 | + GetCardanoTransactionProvider, InsertCardanoTransactionProvider, |
| 66 | + }; |
| 67 | + use crate::database::test_helper::cardano_tx_db_connection; |
| 68 | + use crate::sqlite::GetAllProvider; |
| 69 | + |
| 70 | + use super::*; |
| 71 | + |
| 72 | + fn insert_transactions(connection: &SqliteConnection, records: Vec<CardanoTransactionRecord>) { |
| 73 | + let provider = InsertCardanoTransactionProvider::new(connection); |
| 74 | + let condition = provider.get_insert_many_condition(records).unwrap(); |
| 75 | + let mut cursor = provider.find(condition).unwrap(); |
| 76 | + cursor.next().unwrap(); |
| 77 | + } |
| 78 | + |
| 79 | + fn test_transaction_set() -> Vec<CardanoTransactionRecord> { |
| 80 | + vec![ |
| 81 | + CardanoTransactionRecord::new("tx-hash-0", 10, 50, "block-hash-10", 1), |
| 82 | + CardanoTransactionRecord::new("tx-hash-1", 10, 51, "block-hash-10", 1), |
| 83 | + CardanoTransactionRecord::new("tx-hash-2", 11, 52, "block-hash-11", 1), |
| 84 | + CardanoTransactionRecord::new("tx-hash-3", 11, 53, "block-hash-11", 1), |
| 85 | + CardanoTransactionRecord::new("tx-hash-4", 12, 54, "block-hash-12", 1), |
| 86 | + CardanoTransactionRecord::new("tx-hash-5", 12, 55, "block-hash-12", 1), |
| 87 | + ] |
| 88 | + } |
| 89 | + |
| 90 | + #[test] |
| 91 | + fn test_prune_work_even_without_transactions_in_db() { |
| 92 | + let connection = cardano_tx_db_connection().unwrap(); |
| 93 | + |
| 94 | + let prune_provider = DeleteCardanoTransactionProvider::new(&connection); |
| 95 | + let cursor = prune_provider |
| 96 | + .prune(100) |
| 97 | + .expect("pruning shouldn't crash without transactions stored"); |
| 98 | + assert_eq!(0, cursor.count()); |
| 99 | + } |
| 100 | + |
| 101 | + #[test] |
| 102 | + fn test_prune_all_data_if_given_block_number_is_larger_than_stored_number_of_block() { |
| 103 | + let connection = cardano_tx_db_connection().unwrap(); |
| 104 | + insert_transactions(&connection, test_transaction_set()); |
| 105 | + |
| 106 | + let prune_provider = DeleteCardanoTransactionProvider::new(&connection); |
| 107 | + let cursor = prune_provider.prune(100_000).unwrap(); |
| 108 | + assert_eq!(test_transaction_set().len(), cursor.count()); |
| 109 | + |
| 110 | + let get_provider = GetCardanoTransactionProvider::new(&connection); |
| 111 | + let cursor = get_provider.get_all().unwrap(); |
| 112 | + assert_eq!(0, cursor.count()); |
| 113 | + } |
| 114 | + |
| 115 | + #[test] |
| 116 | + fn test_prune_keep_all_tx_of_last_block_if_given_number_of_block_is_zero() { |
| 117 | + let connection = cardano_tx_db_connection().unwrap(); |
| 118 | + insert_transactions(&connection, test_transaction_set()); |
| 119 | + |
| 120 | + let prune_provider = DeleteCardanoTransactionProvider::new(&connection); |
| 121 | + let cursor = prune_provider.prune(0).unwrap(); |
| 122 | + assert_eq!(0, cursor.count()); |
| 123 | + |
| 124 | + let get_provider = GetCardanoTransactionProvider::new(&connection); |
| 125 | + let cursor = get_provider.get_all().unwrap(); |
| 126 | + assert_eq!(test_transaction_set().len(), cursor.count()); |
| 127 | + } |
| 128 | + |
| 129 | + #[test] |
| 130 | + fn test_prune_data_of_below_given_blocks() { |
| 131 | + let connection = cardano_tx_db_connection().unwrap(); |
| 132 | + insert_transactions(&connection, test_transaction_set()); |
| 133 | + |
| 134 | + let prune_provider = DeleteCardanoTransactionProvider::new(&connection); |
| 135 | + let cursor = prune_provider.prune(12).unwrap(); |
| 136 | + assert_eq!(4, cursor.count()); |
| 137 | + |
| 138 | + let get_provider = GetCardanoTransactionProvider::new(&connection); |
| 139 | + let cursor = get_provider.get_all().unwrap(); |
| 140 | + assert_eq!(2, cursor.count()); |
| 141 | + } |
| 142 | +} |
0 commit comments