Skip to main content

elph_agent/datastore/
migrations.rs

1use turso::Connection;
2
3use super::DatastoreError;
4
5pub struct Migration {
6    pub version: i64,
7    pub name: &'static str,
8    pub up: &'static str,
9}
10
11pub async fn run(conn: &Connection, migrations: &[Migration]) -> Result<(), DatastoreError> {
12    conn.execute(
13        "CREATE TABLE IF NOT EXISTS app_migrations (
14            id INTEGER PRIMARY KEY AUTOINCREMENT,
15            version INTEGER NOT NULL,
16            name TEXT NOT NULL,
17            applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
18        )",
19        (),
20    )
21    .await?;
22
23    conn.execute(
24        "CREATE UNIQUE INDEX IF NOT EXISTS idx_app_migrations_version
25         ON app_migrations(version)",
26        (),
27    )
28    .await?;
29
30    let mut rows = conn
31        .query("SELECT COALESCE(MAX(version), 0) FROM app_migrations", ())
32        .await?;
33
34    let current_version = if let Some(row) = rows.next().await? {
35        row.get::<i64>(0)?
36    } else {
37        0
38    };
39
40    for migration in migrations {
41        if migration.version <= current_version {
42            continue;
43        }
44
45        for statement in split_sql(migration.up) {
46            conn.execute(statement, ()).await?;
47        }
48
49        conn.execute(
50            "INSERT INTO app_migrations (version, name) VALUES (?, ?)",
51            (migration.version, migration.name),
52        )
53        .await?;
54    }
55
56    Ok(())
57}
58
59fn split_sql(sql: &str) -> Vec<&str> {
60    sql.split(';')
61        .map(str::trim)
62        .filter(|statement| !statement.is_empty())
63        .collect()
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn split_sql_handles_multiple_statements() {
72        let parts = split_sql("CREATE TABLE a (id INT); CREATE INDEX idx ON a(id);");
73        assert_eq!(parts.len(), 2);
74    }
75}