mixtape_tools/sqlite/migration/
get.rs1use crate::prelude::*;
4use crate::sqlite::error::SqliteToolError;
5use crate::sqlite::manager::with_connection;
6
7use super::{ensure_migrations_table, MIGRATIONS_TABLE};
8
9#[derive(Debug, Deserialize, JsonSchema)]
11pub struct GetMigrationInput {
12 pub version: String,
14
15 #[serde(default)]
17 pub db_path: Option<String>,
18}
19
20pub struct GetMigrationTool;
22
23impl Tool for GetMigrationTool {
24 type Input = GetMigrationInput;
25
26 fn name(&self) -> &str {
27 "sqlite_get_migration"
28 }
29
30 fn description(&self) -> &str {
31 "Get full details of a specific migration by version, including the SQL statement."
32 }
33
34 async fn execute(&self, input: Self::Input) -> Result<ToolResult, ToolError> {
35 let version_input = input.version;
36
37 let (version, name, sql, applied_at, checksum) =
38 with_connection(input.db_path, move |conn| {
39 ensure_migrations_table(conn)?;
41
42 let query = format!(
43 "SELECT version, name, sql, applied_at, checksum FROM {MIGRATIONS_TABLE} \
44 WHERE version = ?1"
45 );
46
47 let migration = conn.query_row(&query, [&version_input], |row| {
48 let version: String = row.get(0)?;
49 let name: String = row.get(1)?;
50 let sql: String = row.get(2)?;
51 let applied_at: Option<String> = row.get(3)?;
52 let checksum: String = row.get(4)?;
53
54 Ok((version, name, sql, applied_at, checksum))
55 });
56
57 match migration {
58 Ok(m) => Ok(m),
59 Err(rusqlite::Error::QueryReturnedNoRows) => {
60 Err(SqliteToolError::MigrationNotFound(version_input))
61 }
62 Err(e) => Err(e.into()),
63 }
64 })
65 .await?;
66
67 Ok(ToolResult::Json(serde_json::json!({
68 "status": "success",
69 "version": version,
70 "name": name,
71 "sql": sql,
72 "migration_status": if applied_at.is_some() { "applied" } else { "pending" },
73 "applied_at": applied_at,
74 "checksum": checksum
75 })))
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 use crate::sqlite::migration::add::AddMigrationInput;
83 use crate::sqlite::migration::AddMigrationTool;
84 use crate::sqlite::test_utils::{unwrap_json, TestDatabase};
85
86 #[tokio::test]
87 async fn test_get_migration() {
88 let db = TestDatabase::new().await;
89
90 let add_tool = AddMigrationTool;
92 let add_result = add_tool
93 .execute(AddMigrationInput {
94 name: "create users table".to_string(),
95 sql: "CREATE TABLE users (id INTEGER PRIMARY KEY);".to_string(),
96 db_path: Some(db.key()),
97 })
98 .await
99 .unwrap();
100
101 let add_json = unwrap_json(add_result);
102 let version = add_json["version"].as_str().unwrap().to_string();
103
104 let get_tool = GetMigrationTool;
106 let result = get_tool
107 .execute(GetMigrationInput {
108 version: version.clone(),
109 db_path: Some(db.key()),
110 })
111 .await
112 .unwrap();
113
114 let json = unwrap_json(result);
115
116 assert_eq!(json["status"], "success");
117 assert_eq!(json["version"], version);
118 assert_eq!(json["name"], "create users table");
119 assert_eq!(json["sql"], "CREATE TABLE users (id INTEGER PRIMARY KEY);");
120 assert_eq!(json["migration_status"], "pending");
121 assert!(json["checksum"].as_str().unwrap().len() == 64);
122 }
123
124 #[tokio::test]
125 async fn test_get_migration_not_found() {
126 let db = TestDatabase::new().await;
127
128 let tool = GetMigrationTool;
129 let result = tool
130 .execute(GetMigrationInput {
131 version: "nonexistent".to_string(),
132 db_path: Some(db.key()),
133 })
134 .await;
135
136 assert!(result.is_err());
137 let err = result.unwrap_err();
138 assert!(err.to_string().contains("Migration not found"));
139 }
140}