keepsake-sqlx 1.0.0

SQLx adapter for keepsake lifecycle storage
Documentation
use super::backend::KeepsakeSqlxBackend;
use super::{RepositoryError, RepositoryResult};
#[cfg(feature = "mysql")]
use super::MySqlBackend;
#[cfg(feature = "postgres")]
use super::PostgresBackend;
#[cfg(feature = "sqlite")]
use super::SqliteBackend;

#[cfg(all(feature = "postgres", feature = "migrations"))]
pub(super) async fn postgres_schema_preflight(pool: &sqlx::PgPool) -> RepositoryResult<()> {
    let metadata_table = sqlx::query_scalar::<_, Option<String>>(
        r"
        select to_regclass('public.keepsake_schema_metadata')::text
        ",
    )
    .fetch_one(pool)
    .await?;

    if metadata_table.is_none() {
        return postgres_unmarked_schema_preflight(pool).await;
    }

    let backend = sqlx::query_scalar::<_, Option<String>>(
        r"
        select value
        from keepsake_schema_metadata
        where key = 'backend'
        ",
    )
    .fetch_one(pool)
    .await?;

    match backend.as_deref() {
        Some(PostgresBackend::NAME) | None => Ok(()),
        Some(actual) => Err(RepositoryError::BackendMismatch {
            expected: PostgresBackend::NAME,
            actual: actual.to_owned(),
        }),
    }
}

#[cfg(all(feature = "postgres", feature = "migrations"))]
async fn postgres_unmarked_schema_preflight(pool: &sqlx::PgPool) -> RepositoryResult<()> {
    let user_table_count = sqlx::query_scalar::<_, i64>(
        r"
        select count(*)
        from information_schema.tables
        where table_schema = 'public'
          and table_type = 'BASE TABLE'
        ",
    )
    .fetch_one(pool)
    .await?;

    if user_table_count == 0 {
        return Ok(());
    }

    let has_keepsake_tables = sqlx::query_scalar::<_, bool>(
        r"
        select to_regclass('public.keepsake_relation_definitions') is not null
           and to_regclass('public.keepsakes') is not null
           and to_regclass('public._sqlx_migrations') is not null
        ",
    )
    .fetch_one(pool)
    .await?;
    if !has_keepsake_tables {
        return Err(RepositoryError::BackendMismatch {
            expected: PostgresBackend::NAME,
            actual: "unmarked non-empty schema".to_owned(),
        });
    }

    let known_migrations = sqlx::query_scalar::<_, i64>(
        r"
        select count(*)
        from _sqlx_migrations
        where version in (1, 2)
        ",
    )
    .fetch_one(pool)
    .await?;

    if known_migrations == 2 {
        Ok(())
    } else {
        Err(RepositoryError::BackendMismatch {
            expected: PostgresBackend::NAME,
            actual: "unmarked unknown migration history".to_owned(),
        })
    }
}

#[cfg(all(feature = "sqlite", feature = "migrations"))]
pub(super) async fn sqlite_schema_preflight(pool: &sqlx::SqlitePool) -> RepositoryResult<()> {
    let metadata_table = sqlx::query_scalar::<_, Option<String>>(
        r"
        select name
        from sqlite_master
        where type = 'table' and name = 'keepsake_schema_metadata'
        ",
    )
    .fetch_optional(pool)
    .await?
    .flatten();

    if metadata_table.is_some() {
        let backend = sqlx::query_scalar::<_, Option<String>>(
            r"
            select value
            from keepsake_schema_metadata
            where key = 'backend'
            ",
        )
        .fetch_one(pool)
        .await?;
        return match backend.as_deref() {
            Some(SqliteBackend::NAME) | None => Ok(()),
            Some(actual) => Err(RepositoryError::BackendMismatch {
                expected: SqliteBackend::NAME,
                actual: actual.to_owned(),
            }),
        };
    }

    let existing_tables = sqlx::query_scalar::<_, i64>(
        r"
        select count(*)
        from sqlite_master
        where type = 'table'
          and name not like 'sqlite_%'
        ",
    )
    .fetch_one(pool)
    .await?;

    if existing_tables == 0 {
        Ok(())
    } else {
        Err(RepositoryError::BackendMismatch {
            expected: SqliteBackend::NAME,
            actual: "unmarked non-empty schema".to_owned(),
        })
    }
}

#[cfg(all(feature = "mysql", feature = "migrations"))]
pub(super) async fn mysql_schema_preflight(pool: &sqlx::MySqlPool) -> RepositoryResult<()> {
    let metadata_table = sqlx::query_scalar::<_, Option<String>>(
        r"
        select table_name
        from information_schema.tables
        where table_schema = database()
          and table_name = 'keepsake_schema_metadata'
        ",
    )
    .fetch_optional(pool)
    .await?
    .flatten();

    if metadata_table.is_some() {
        let backend = sqlx::query_scalar::<_, Option<String>>(
            r"
            select value
            from keepsake_schema_metadata
            where `key` = 'backend'
            ",
        )
        .fetch_one(pool)
        .await?;
        return match backend.as_deref() {
            Some(MySqlBackend::NAME) | None => Ok(()),
            Some(actual) => Err(RepositoryError::BackendMismatch {
                expected: MySqlBackend::NAME,
                actual: actual.to_owned(),
            }),
        };
    }

    let existing_tables = sqlx::query_scalar::<_, i64>(
        r"
        select count(*)
        from information_schema.tables
        where table_schema = database()
        ",
    )
    .fetch_one(pool)
    .await?;

    if existing_tables == 0 {
        Ok(())
    } else {
        Err(RepositoryError::BackendMismatch {
            expected: MySqlBackend::NAME,
            actual: "unmarked non-empty schema".to_owned(),
        })
    }
}