miden_node_db/migration/
entry.rs1use std::fmt;
2
3use anyhow::{Context, Result, ensure};
4use rusqlite::{Connection, Transaction};
5
6use super::schema::{self, SchemaHash};
7
8pub(super) trait MigrationEntry {
10 fn name(&self) -> &'static str;
12
13 fn execute_migration(&self, tx: &Transaction<'_>) -> Result<()>;
15}
16
17pub(super) struct SqlMigration {
19 name: &'static str,
20 sql: &'static str,
21}
22
23impl SqlMigration {
24 pub(super) fn new(name: &'static str, sql: &'static str) -> Self {
25 Self { name, sql }
26 }
27}
28
29impl MigrationEntry for SqlMigration {
30 fn name(&self) -> &'static str {
31 self.name
32 }
33
34 fn execute_migration(&self, tx: &Transaction<'_>) -> Result<()> {
35 tx.execute_batch(self.sql).map_err(Into::into)
36 }
37}
38
39impl fmt::Debug for SqlMigration {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 f.debug_struct("SqlMigration").field("name", &self.name).finish_non_exhaustive()
42 }
43}
44
45pub(super) struct CodeMigration {
47 name: &'static str,
48 apply: CodeMigrationFn,
49}
50
51impl CodeMigration {
52 pub(super) fn new(name: &'static str, apply: CodeMigrationFn) -> Self {
53 Self { name, apply }
54 }
55}
56
57impl MigrationEntry for CodeMigration {
58 fn name(&self) -> &'static str {
59 self.name
60 }
61
62 fn execute_migration(&self, tx: &Transaction<'_>) -> Result<()> {
63 (self.apply)(tx)
64 }
65}
66
67impl fmt::Debug for CodeMigration {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 f.debug_struct("CodeMigration")
70 .field("name", &self.name)
71 .finish_non_exhaustive()
72 }
73}
74
75pub(super) enum Migration {
77 Sql(SqlMigration),
78 Code(CodeMigration),
79}
80
81impl Migration {
82 pub(super) fn sql(name: &'static str, sql: &'static str) -> Self {
83 Self::Sql(SqlMigration::new(name, sql))
84 }
85
86 pub(super) fn code(name: &'static str, apply: CodeMigrationFn) -> Self {
87 Self::Code(CodeMigration::new(name, apply))
88 }
89}
90
91impl MigrationEntry for Migration {
92 fn name(&self) -> &'static str {
93 match self {
94 Self::Sql(migration) => migration.name(),
95 Self::Code(migration) => migration.name(),
96 }
97 }
98
99 fn execute_migration(&self, tx: &Transaction<'_>) -> Result<()> {
100 match self {
101 Self::Sql(migration) => migration.execute_migration(tx),
102 Self::Code(migration) => migration.execute_migration(tx),
103 }
104 }
105}
106
107impl fmt::Debug for Migration {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 match self {
110 Self::Sql(migration) => fmt::Debug::fmt(migration, f),
111 Self::Code(migration) => fmt::Debug::fmt(migration, f),
112 }
113 }
114}
115
116pub(super) fn apply_migration(
118 conn: &mut Connection,
119 version: usize,
120 migration: &impl MigrationEntry,
121) -> Result<SchemaHash> {
122 apply_migration_transaction(conn, version, migration, Ok::<SchemaHash, anyhow::Error>)
123}
124
125pub(super) fn apply_migration_and_verify_schema(
127 conn: &mut Connection,
128 version: usize,
129 migration: &impl MigrationEntry,
130 expected: SchemaHash,
131) -> Result<()> {
132 apply_migration_transaction(conn, version, migration, |actual| {
133 ensure!(actual == expected, "schema hash mismatch: expected {expected}, got {actual}");
134 Ok(())
135 })
136}
137
138fn apply_migration_transaction<T>(
139 conn: &mut Connection,
140 version: usize,
141 migration: &impl MigrationEntry,
142 verify_hash: impl FnOnce(SchemaHash) -> Result<T>,
143) -> Result<T> {
144 let tx = conn.transaction().context("failed to start transaction")?;
145
146 migration.execute_migration(&tx).context("failed to execute migration")?;
147 let schema_hash = SchemaHash::new(&tx).context("failed to compute schema hash")?;
148 let result = verify_hash(schema_hash)?;
149 schema::set_version(&tx, version).context("failed to update user_version")?;
150 tx.commit().context("failed to commit transaction")?;
151
152 Ok(result)
153}
154
155pub type CodeMigrationFn = for<'conn> fn(&Transaction<'conn>) -> Result<()>;