mithril_signer/database/query/signed_beacon/
insert_signed_beacon.rs

1use sqlite::Value;
2
3use mithril_common::StdResult;
4use mithril_persistence::sqlite::{Query, SourceAlias, SqLiteEntity, WhereCondition};
5
6use crate::database::record::SignedBeaconRecord;
7
8/// Query to insert or replace [SignedBeaconRecord] in the sqlite database
9pub struct InsertSignedBeaconRecordQuery {
10    condition: WhereCondition,
11}
12
13impl InsertSignedBeaconRecordQuery {
14    pub fn one(record: SignedBeaconRecord) -> StdResult<Self> {
15        let condition = WhereCondition::new(
16            "(epoch, beacon, signed_entity_type_id, initiated_at, signed_at) values (?*, ?*, ?*, ?*, ?*)",
17            vec![
18                Value::Integer(record.epoch.try_into()?),
19                Value::String(record.signed_entity_type.get_json_beacon()?),
20                Value::Integer(record.signed_entity_type.index() as i64),
21                Value::String(record.initiated_at.to_rfc3339()),
22                Value::String(record.signed_at.to_rfc3339()),
23            ],
24        );
25
26        Ok(Self { condition })
27    }
28}
29
30impl Query for InsertSignedBeaconRecordQuery {
31    type Entity = SignedBeaconRecord;
32
33    fn filters(&self) -> WhereCondition {
34        self.condition.clone()
35    }
36
37    fn get_definition(&self, condition: &str) -> String {
38        // it is important to alias the fields with the same name as the table
39        // since the table cannot be aliased in a RETURNING statement in SQLite.
40        let projection = Self::Entity::get_projection()
41            .expand(SourceAlias::new(&[("{:signed_beacon:}", "signed_beacon")]));
42
43        format!("insert into signed_beacon {condition} returning {projection}")
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use mithril_common::entities::{Epoch, SignedEntityType};
50    use mithril_persistence::sqlite::ConnectionExtensions;
51
52    use crate::database::test_helper::main_db_connection;
53
54    use super::*;
55
56    #[test]
57    fn insert_records_in_empty_db() {
58        let connection = main_db_connection().unwrap();
59
60        let record = SignedBeaconRecord::fake(
61            Epoch(5),
62            SignedEntityType::CardanoStakeDistribution(Epoch(6)),
63        );
64        let inserted_record = connection
65            .fetch_first(InsertSignedBeaconRecordQuery::one(record.clone()).unwrap())
66            .unwrap();
67
68        assert_eq!(Some(record), inserted_record);
69    }
70
71    #[test]
72    #[should_panic]
73    fn inserting_same_record_twice_should_fail() {
74        let connection = main_db_connection().unwrap();
75
76        let record = SignedBeaconRecord::fake(
77            Epoch(13),
78            SignedEntityType::CardanoStakeDistribution(Epoch(17)),
79        );
80
81        connection
82            .fetch_first(InsertSignedBeaconRecordQuery::one(record.clone()).unwrap())
83            .unwrap();
84        let _ = connection.fetch_first(InsertSignedBeaconRecordQuery::one(record.clone()).unwrap());
85    }
86}