elph_agent/datastore/
migrations.rs1use 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}