libchat/core/sqlite/src/migrations.rs
kaichao c44c52b127
feat: storage implementation and trait abstraction (#79)
* feat: storage for conversations

* fix: db types conversion

* feat: run migrations from sql files

* feat: persist identity

* fix: revert double ratchet storage refactor

* fix: clean

* refactor: use result wrapper for ffi

* refactor: uniform storage error into chat error

* fix: zeroize identity record

* fix: zeroize for secret keys in db operations

* fix: transactional sql migration

* fix: remove destroy_string

* feat: db storage for inbox ephermeral keys

* chore: remove in memory hashmap for ephemeral keys

* feat: persist conversation store

* feat: wire with the double ratchet storage

* feat: remove conversation store

* chore: fix conversation type not used

* feat: mock chat store implementation

* chore: sqlite module

* feat: sqlite crate

* chore: sqlite rename

* chore: more refactor

* extract ratchet store trait

* chore: clear error conversion

* chore: remove customized db conn

* chore: fix clippy

* chore: refactor to use generics and enum

* chore: further clean for review comments
2026-04-03 08:25:26 +08:00

60 lines
1.9 KiB
Rust

//! Database migrations module.
//!
//! SQL migrations are embedded at compile time and applied in order.
//! Each migration is applied atomically within a transaction.
use rusqlite::Connection;
use storage::StorageError;
use crate::errors::map_rusqlite_error;
/// Embeds and returns all migration SQL files in order.
pub fn get_migrations() -> Vec<(&'static str, &'static str)> {
vec![
(
"001_initial_schema",
include_str!("migrations/001_initial_schema.sql"),
),
(
"002_ratchet_state",
include_str!("migrations/002_ratchet_state.sql"),
),
]
}
/// Applies all migrations to the database.
///
/// Uses a simple version tracking table to avoid re-running migrations.
pub fn apply_migrations(conn: &mut Connection) -> Result<(), StorageError> {
// Create migrations tracking table if it doesn't exist
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS _migrations (
name TEXT PRIMARY KEY,
applied_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
);",
)
.map_err(map_rusqlite_error)?;
for (name, sql) in get_migrations() {
// Check if migration already applied
let already_applied: bool = conn
.query_row(
"SELECT EXISTS(SELECT 1 FROM _migrations WHERE name = ?1)",
[name],
|row| row.get(0),
)
.map_err(map_rusqlite_error)?;
if !already_applied {
// Apply migration and record it atomically in a transaction
let tx = conn.transaction().map_err(map_rusqlite_error)?;
tx.execute_batch(sql).map_err(map_rusqlite_error)?;
tx.execute("INSERT INTO _migrations (name) VALUES (?1)", [name])
.map_err(map_rusqlite_error)?;
tx.commit().map_err(map_rusqlite_error)?;
}
}
Ok(())
}