systemprompt_database/lifecycle/migrations/
mark_applied.rs1use super::MigrationService;
14use systemprompt_extension::{Extension, LoaderError};
15use systemprompt_identifiers::ExtensionId;
16
17#[derive(Debug, Clone)]
18pub struct MarkAppliedOutcome {
19 pub extension_id: ExtensionId,
20 pub version: u32,
21 pub name: String,
22 pub checksum: String,
23}
24
25impl MigrationService<'_> {
26 pub async fn mark_applied(
27 &self,
28 extension: &dyn Extension,
29 version: u32,
30 ) -> Result<MarkAppliedOutcome, LoaderError> {
31 let ext_id = &ExtensionId::new(extension.metadata().id);
32
33 let migration = extension
34 .migrations()
35 .into_iter()
36 .find(|m| m.version == version)
37 .ok_or_else(|| LoaderError::MigrationFailed {
38 extension: ext_id.clone(),
39 message: format!(
40 "Migration version {version} is not defined for extension '{ext_id}'"
41 ),
42 })?;
43
44 if migration.tombstone {
45 return Err(LoaderError::MigrationFailed {
46 extension: ext_id.clone(),
47 message: format!(
48 "Migration {version} ('{}') is a tombstone: the slot is recorded as spent and \
49 has no SQL, so there is nothing to mark applied",
50 migration.name
51 ),
52 });
53 }
54
55 self.ensure_migrations_table_exists().await?;
56
57 let applied = self.get_applied_migrations(ext_id).await?;
58 if applied.iter().any(|m| m.version == version) {
59 return Err(LoaderError::MigrationFailed {
60 extension: ext_id.clone(),
61 message: format!(
62 "Migration {version} ('{}') is already tracked as applied for extension \
63 '{ext_id}'; nothing to do",
64 migration.name
65 ),
66 });
67 }
68
69 let id = format!("{ext_id}_{:03}", migration.version);
70 let checksum = migration.checksum();
71
72 self.db
73 .execute(
74 &"INSERT INTO extension_migrations (id, extension_id, version, name, checksum) \
75 VALUES ($1, $2, $3, $4, $5)",
76 &[&id, &ext_id, &migration.version, &migration.name, &checksum],
77 )
78 .await
79 .map_err(|e| LoaderError::MigrationStepFailed {
80 extension: ext_id.clone(),
81 context: "Failed to record migration as applied".to_owned(),
82 source: Box::new(e),
83 })?;
84
85 Ok(MarkAppliedOutcome {
86 extension_id: ext_id.clone(),
87 version: migration.version,
88 name: migration.name.clone(),
89 checksum,
90 })
91 }
92}