libsql_migration 0.2.2

A simple SQL migration tool for libsql databases
Documentation
use std::path::Path;

use crate::errors::{LibsqlDirMigratorError, LibsqlMigratorBaseError};
use libsql::Connection;

pub(crate) async fn create_migration_table(
    conn: &Connection,
) -> Result<(), LibsqlMigratorBaseError> {
    let sql_query = include_str!("./base_migration_table.sql");

    conn.execute(sql_query, libsql::params![]).await?;
    Ok(())
}

pub(crate) fn validate_migration_folder(path: &Path) -> Result<(), LibsqlDirMigratorError> {
    if !path.exists() {
        return Err(LibsqlDirMigratorError::MigrationDirNotFound(
            path.to_path_buf(),
        ));
    };
    if path.is_dir() || (path.is_file() && path.extension().unwrap_or_default() == "sql") {
        Ok(())
    } else {
        Err(LibsqlDirMigratorError::InvalidMigrationPath(
            path.to_path_buf(),
        ))
    }
}

#[derive(Debug, PartialEq)]
pub enum MigrationResult {
    Executed,
    AlreadyExecuted,
}

pub(crate) async fn execute_migration(
    conn: &Connection,
    id: String,
    sql_script: String,
) -> Result<MigrationResult, LibsqlMigratorBaseError> {
    let mut stmt = conn
        .prepare("SELECT status FROM libsql_migrations WHERE id = ?;")
        .await?;

    let mut rows = stmt.query([id.clone()]).await?;

    if let Some(record) = rows.next().await? {
        let status_value = record.get_value(0)?;
        if let libsql::Value::Integer(1) = status_value {
            return Ok(MigrationResult::AlreadyExecuted);
        }
    } else {
        conn.execute(
            "INSERT INTO libsql_migrations (id) VALUES (?) ON CONFLICT(id) DO NOTHING",
            libsql::params![id.clone()],
        )
        .await?;
    }

    conn.execute(&sql_script, libsql::params!()).await?;

    conn.execute(
        "UPDATE libsql_migrations SET status = true, exec_time = CURRENT_TIMESTAMP WHERE id = ?",
        libsql::params![id],
    )
    .await?;

    Ok(MigrationResult::Executed)
}