Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get rid of store adapter in signer #2126

Merged
merged 14 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion internal/mithril-persistence/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mithril-persistence"
version = "0.2.34"
version = "0.2.35"
description = "Common types, interfaces, and utilities to persist data for Mithril nodes."
authors = { workspace = true }
edition = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ insert into db_version (application_type, version, updated_at) values ('{applica
}

/// Represent a file containing SQL structure or data alterations.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct SqlMigration {
/// The semver version this migration targets.
pub version: DbVersion,
Expand Down
74 changes: 58 additions & 16 deletions internal/mithril-persistence/src/sqlite/connection_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,21 +107,8 @@ impl ConnectionBuilder {
.with_context(|| "SQLite initialization: could not enable FOREIGN KEY support.")?;
}

if self.sql_migrations.is_empty().not() {
// Check database migrations
debug!(logger, "Applying database migrations");
let mut db_checker =
DatabaseVersionChecker::new(self.base_logger, self.node_type, &connection);

for migration in self.sql_migrations {
db_checker.add_migration(migration);
}

db_checker
.apply()
.with_context(|| "Database migration error")?;
}

let migrations = self.sql_migrations.clone();
self.apply_migrations(&connection, migrations)?;
if self
.options
.contains(&ConnectionOptions::ForceDisableForeignKeys)
Expand All @@ -131,9 +118,37 @@ impl ConnectionBuilder {
.execute("pragma foreign_keys=false")
.with_context(|| "SQLite initialization: could not disable FOREIGN KEY support.")?;
}

Ok(connection)
}

/// Apply a list of migration to the connection.
pub fn apply_migrations(
&self,
connection: &ConnectionThreadSafe,
sql_migrations: Vec<SqlMigration>,
) -> StdResult<()> {
let logger = self.base_logger.new_with_component_name::<Self>();

if sql_migrations.is_empty().not() {
// Check database migrations
debug!(logger, "Applying database migrations");
let mut db_checker = DatabaseVersionChecker::new(
self.base_logger.clone(),
self.node_type.clone(),
connection,
);

for migration in sql_migrations {
db_checker.add_migration(migration.clone());
}

db_checker
.apply()
.with_context(|| "Database migration error")?;
}

Ok(())
}
}

#[cfg(test)]
Expand Down Expand Up @@ -278,4 +293,31 @@ mod tests {
let foreign_keys = execute_single_cell_query(&connection, "pragma foreign_keys;");
assert_eq!(Value::Integer(false.into()), foreign_keys);
}

#[test]
fn test_apply_a_partial_migrations() {
let migrations = vec![
SqlMigration::new(1, "create table first(id integer);"),
SqlMigration::new(2, "create table second(id integer);"),
];

let connection = ConnectionBuilder::open_memory().build().unwrap();

assert!(connection.prepare("select * from first;").is_err());
assert!(connection.prepare("select * from second;").is_err());

ConnectionBuilder::open_memory()
.apply_migrations(&connection, migrations[0..1].to_vec())
.unwrap();

assert!(connection.prepare("select * from first;").is_ok());
assert!(connection.prepare("select * from second;").is_err());

ConnectionBuilder::open_memory()
.apply_migrations(&connection, migrations)
.unwrap();

assert!(connection.prepare("select * from first;").is_ok());
assert!(connection.prepare("select * from second;").is_ok());
}
}
2 changes: 1 addition & 1 deletion mithril-signer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mithril-signer"
version = "0.2.214"
version = "0.2.215"
description = "A Mithril Signer"
authors = { workspace = true }
edition = { workspace = true }
Expand Down
51 changes: 50 additions & 1 deletion mithril-signer/src/database/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,61 @@ create index signed_beacon_signed_entity_type_id on signed_beacon(signed_entity_
// Migration 4
// Remove `network` from cardano immutable files full beacons in `signed_beacon` table
SqlMigration::new(
31,
4,
r#"
update signed_beacon
set beacon = json_remove(beacon, '$.network')
where signed_beacon.signed_entity_type_id = 2;
"#,
),
// Migration 5
// Add the `stake_pool` table and migration data from the previous
// `stake_store` JSON format.
SqlMigration::new(
5,
r#"
create table stake_pool (
stake_pool_id text not null,
epoch integer not null,
stake integer not null,
created_at text not null,
primary key (epoch, stake_pool_id)
);
create table if not exists stake (key_hash text primary key, key json not null, value json not null);
insert into stake_pool (epoch, stake_pool_id, stake, created_at)
select
stake.key as epoch,
stake_dis.key as stake_pool_id,
stake_dis.value as stake,
strftime('%Y-%m-%dT%H:%M:%fZ', current_timestamp)
from stake, json_each(stake.value) as stake_dis
order by epoch asc;
drop table stake;
"#,
),
// Migration 6
// Add the `protocol_initializer` table and migration data from the previous
// `protocol_initializer` JSON format.
SqlMigration::new(
6,
r#"
create table new_protocol_initializer (
epoch integer not null,
protocol json not null,
created_at text not null,
primary key (epoch)
);
create table if not exists protocol_initializer (key_hash text primary key, key json not null, value json not null);
jpraynaud marked this conversation as resolved.
Show resolved Hide resolved
insert into new_protocol_initializer (epoch, protocol, created_at)
select
protocol_initializer.key as epoch,
protocol_initializer.value,
strftime('%Y-%m-%dT%H:%M:%fZ', current_timestamp)
from protocol_initializer
order by epoch asc;
drop table protocol_initializer;
alter table new_protocol_initializer rename to protocol_initializer;
"#,
),
]
}
2 changes: 2 additions & 0 deletions mithril-signer/src/database/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ pub mod record;
pub mod repository;
#[cfg(test)]
pub(crate) mod test_helper;
#[cfg(test)]
pub(crate) mod tests;
4 changes: 4 additions & 0 deletions mithril-signer/src/database/query/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
//! Signer related database queries

mod protocol_initializer;
mod signed_beacon;
mod stake_pool;

pub use protocol_initializer::*;
pub use signed_beacon::*;
pub use stake_pool::*;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use sqlite::Value;

use mithril_common::entities::Epoch;
use mithril_persistence::sqlite::{Query, SourceAlias, SqLiteEntity, WhereCondition};

use crate::database::record::ProtocolInitializerRecord;

/// Query to delete old [ProtocolInitializer] from the sqlite database
pub struct DeleteProtocolInitializerQuery {
condition: WhereCondition,
}

impl Query for DeleteProtocolInitializerQuery {
type Entity = ProtocolInitializerRecord;

fn filters(&self) -> WhereCondition {
self.condition.clone()
}

fn get_definition(&self, condition: &str) -> String {
// it is important to alias the fields with the same name as the table
// since the table cannot be aliased in a RETURNING statement in SQLite.
let projection = Self::Entity::get_projection().expand(SourceAlias::new(&[(
"{:protocol_initializer:}",
"protocol_initializer",
)]));

format!("delete from protocol_initializer where {condition} returning {projection}")
}
}

impl DeleteProtocolInitializerQuery {
/// Create the SQL query to prune data older than the given Epoch.
pub fn below_epoch_threshold(epoch_threshold: Epoch) -> Self {
let condition = WhereCondition::new(
"epoch < ?*",
vec![Value::Integer(epoch_threshold.try_into().unwrap())],
);

Self { condition }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use sqlite::Value;

use mithril_common::entities::Epoch;
use mithril_persistence::sqlite::{Query, SourceAlias, SqLiteEntity, WhereCondition};

use crate::database::record::ProtocolInitializerRecord;

/// Simple queries to retrieve [ProtocolInitializer] from the sqlite database.
pub struct GetProtocolInitializerQuery {
condition: WhereCondition,
limit: Option<usize>,
}

impl GetProtocolInitializerQuery {
/// Get protocol initializer that match the epoch.
pub fn for_epoch(epoch: Epoch) -> Self {
let epoch_i64: i64 = epoch.try_into().unwrap();
let condition = WhereCondition::new(
"protocol_initializer.epoch = ?",
vec![Value::Integer(epoch_i64)],
);

Self {
condition,
limit: None,
}
}

pub fn last_n(limit: usize) -> Self {
let condition = WhereCondition::default();
Self {
condition,
limit: Some(limit),
}
}
}

impl Query for GetProtocolInitializerQuery {
type Entity = ProtocolInitializerRecord;

fn filters(&self) -> WhereCondition {
self.condition.clone()
}

fn get_definition(&self, condition: &str) -> String {
let aliases = SourceAlias::new(&[("{:protocol_initializer:}", "protocol_initializer")]);
let projection = Self::Entity::get_projection().expand(aliases);
let limit = self
.limit
.map_or("".to_string(), |limit| format!(" limit {}", limit));
format!("select {projection} from protocol_initializer where {condition} order by rowid desc{limit}")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use sqlite::Value;

use mithril_common::StdResult;
use mithril_persistence::sqlite::{Query, SourceAlias, SqLiteEntity, WhereCondition};

use crate::database::record::ProtocolInitializerRecord;

/// Query to insert or replace [ProtocolInitializerRecord] in the sqlite database
pub struct InsertOrReplaceProtocolInitializerQuery {
condition: WhereCondition,
}

impl InsertOrReplaceProtocolInitializerQuery {
pub fn one(record: ProtocolInitializerRecord) -> StdResult<Self> {
let value = serde_json::to_string(&record.protocol_initializer).unwrap();
let condition = WhereCondition::new(
"(epoch, protocol, created_at) values (?*, ?*, ?*)",
vec![
Value::Integer(record.epoch.try_into()?),
Value::String(value),
Value::String(record.created_at.to_rfc3339()),
],
);

Ok(Self { condition })
}
}

impl Query for InsertOrReplaceProtocolInitializerQuery {
type Entity = ProtocolInitializerRecord;

fn filters(&self) -> WhereCondition {
self.condition.clone()
}

fn get_definition(&self, condition: &str) -> String {
// it is important to alias the fields with the same name as the table
// since the table cannot be aliased in a RETURNING statement in SQLite.
let projection = Self::Entity::get_projection().expand(SourceAlias::new(&[(
"{:protocol_initializer:}",
"protocol_initializer",
)]));

format!("insert or replace into protocol_initializer {condition} returning {projection}")
}
}
7 changes: 7 additions & 0 deletions mithril-signer/src/database/query/protocol_initializer/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod delete_protocol_initializer;
mod get_protocol_initializer;
mod insert_protocol_initializer;

pub use delete_protocol_initializer::*;
pub use get_protocol_initializer::*;
pub use insert_protocol_initializer::*;
Loading