Skip to main content

toolu_orm_cli/migrate/
store.rs

1//! Migration record storage, lookup, and table initialization.
2
3use toolu_orm_connection::DbConnection;
4use toolu_orm_core::dialect::Dialect;
5use toolu_orm_core::error::DbCoreError;
6use toolu_orm_core::value::Value;
7
8use super::ddl::migrations_table_ddl;
9use super::error::{map_db, MigrateError};
10
11struct AppliedMigration {
12  name: String,
13}
14
15#[cfg(orm_core_has_postgres)]
16fn map_pg(row: &toolu_orm_core::tokio_postgres::Row) -> Result<AppliedMigration, DbCoreError> {
17  let name: String = row
18    .try_get(0)
19    .map_err(|e: toolu_orm_core::tokio_postgres::Error| DbCoreError::RowMapping(e.to_string()))?;
20  Ok(AppliedMigration { name })
21}
22
23#[cfg(orm_core_has_libsql)]
24fn map_libsql(row: &toolu_orm_core::libsql::Row) -> Result<AppliedMigration, DbCoreError> {
25  let name: String = row
26    .get::<String>(0)
27    .map_err(|e| DbCoreError::RowMapping(e.to_string()))?;
28  Ok(AppliedMigration { name })
29}
30
31#[cfg(orm_core_has_rusqlite)]
32fn map_rusqlite(row: &toolu_orm_core::rusqlite::Row<'_>) -> Result<AppliedMigration, DbCoreError> {
33  let name: String = row
34    .get(0)
35    .map_err(|e| DbCoreError::RowMapping(e.to_string()))?;
36  Ok(AppliedMigration { name })
37}
38
39// The `FromRow` trait shape depends on `toolu-orm-core`'s unified features,
40// NOT on `orm-cli`'s own features. We use `orm_core_has_*` cfgs set by our
41// build.rs (reading DEP_TOOLU_ORM_CORE_* metadata from orm-core's build script)
42// to match the exact trait shape orm-core compiled.
43toolu_orm_core::impl_from_row_for!(single
44  cfg(all(orm_core_has_libsql, not(orm_core_has_postgres), not(orm_core_has_rusqlite))),
45  AppliedMigration, &["name"], from_row, toolu_orm_core::libsql::Row, map_libsql);
46
47toolu_orm_core::impl_from_row_for!(single
48  cfg(all(orm_core_has_postgres, not(orm_core_has_libsql), not(orm_core_has_rusqlite))),
49  AppliedMigration, &["name"], from_row, toolu_orm_core::tokio_postgres::Row, map_pg);
50
51toolu_orm_core::impl_from_row_for!(dual
52  cfg(all(orm_core_has_postgres, orm_core_has_libsql, not(orm_core_has_rusqlite))),
53  AppliedMigration, &["name"],
54  [from_pg_row toolu_orm_core::tokio_postgres::Row => map_pg],
55  [from_libsql_row toolu_orm_core::libsql::Row => map_libsql]);
56
57toolu_orm_core::impl_from_row_for!(dual
58  cfg(all(orm_core_has_postgres, orm_core_has_rusqlite, not(orm_core_has_libsql))),
59  AppliedMigration, &["name"],
60  [from_pg_row toolu_orm_core::tokio_postgres::Row => map_pg],
61  [from_rusqlite_row toolu_orm_core::rusqlite::Row<'_> => map_rusqlite]);
62
63toolu_orm_core::impl_from_row_for!(dual
64  cfg(all(orm_core_has_libsql, orm_core_has_rusqlite, not(orm_core_has_postgres))),
65  AppliedMigration, &["name"],
66  [from_libsql_row toolu_orm_core::libsql::Row => map_libsql],
67  [from_rusqlite_row toolu_orm_core::rusqlite::Row<'_> => map_rusqlite]);
68
69toolu_orm_core::impl_from_row_for!(triple
70  cfg(all(orm_core_has_postgres, orm_core_has_libsql, orm_core_has_rusqlite)),
71  AppliedMigration, &["name"],
72  [from_pg_row toolu_orm_core::tokio_postgres::Row => map_pg],
73  [from_libsql_row toolu_orm_core::libsql::Row => map_libsql],
74  [from_rusqlite_row toolu_orm_core::rusqlite::Row<'_> => map_rusqlite]);
75
76/// # Errors
77///
78/// Returns [`MigrateError::Database`] if the insert fails.
79pub async fn record_migration(
80  conn: &impl DbConnection,
81  name: &str,
82  hash: &str,
83  dialect: Dialect,
84) -> Result<(), MigrateError> {
85  let sql = match dialect {
86    Dialect::Sqlite => "INSERT INTO _migrations (name, hash) VALUES (?1, ?2)",
87    Dialect::Postgres => "INSERT INTO _migrations (name, hash) VALUES ($1, $2)",
88  };
89  conn
90    .execute_sql(
91      sql,
92      vec![Value::Text(name.to_owned()), Value::Text(hash.to_owned())],
93    )
94    .await
95    .map_err(|e| map_db(&e))?;
96  Ok(())
97}
98
99/// # Errors
100///
101/// Returns [`MigrateError::Database`] if the query fails.
102pub async fn get_applied_migrations(conn: &impl DbConnection) -> Result<Vec<String>, MigrateError> {
103  let rows = conn
104    .query_map::<AppliedMigration>("SELECT name FROM _migrations ORDER BY id", vec![])
105    .await
106    .map_err(|e| map_db(&e))?;
107  Ok(rows.into_iter().map(|r| r.name).collect())
108}
109
110/// # Errors
111///
112/// Returns [`MigrateError::Database`] if DDL execution fails.
113pub async fn ensure_migrations_table(
114  conn: &impl DbConnection,
115  dialect: Dialect,
116) -> Result<(), MigrateError> {
117  let ddl = migrations_table_ddl(dialect);
118  conn.execute_batch(&ddl).await.map_err(|e| map_db(&e))?;
119  Ok(())
120}