Skip to main content

mixtape_tools/sqlite/migration/
get.rs

1//! Get migration tool
2
3use crate::prelude::*;
4use crate::sqlite::error::SqliteToolError;
5use crate::sqlite::manager::with_connection;
6
7use super::{ensure_migrations_table, MIGRATIONS_TABLE};
8
9/// Input for getting a specific migration
10#[derive(Debug, Deserialize, JsonSchema)]
11pub struct GetMigrationInput {
12    /// The version identifier of the migration to retrieve
13    pub version: String,
14
15    /// Database to get the migration from (uses default if not specified)
16    #[serde(default)]
17    pub db_path: Option<String>,
18}
19
20/// Gets details of a specific migration including its SQL
21pub 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 exists
40                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        // Add a migration
91        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        // Get the migration
105        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}