durable-db 0.11.3

Database migrations and schema for the durable workflow engine
Documentation
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19

use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(
    Debug,
    Clone,
    PartialEq,
    Eq,
    EnumIter,
    DeriveActiveEnum,
    Serialize,
    Deserialize,
    strum :: Display,
    strum :: EnumString,
)]
// HACK: The embedded quotes make sea-query's Iden::quoted() produce
// "durable"."task_status" — a valid schema-qualified PostgreSQL identifier.
// Without this, SeaORM generates CAST($1 AS "task_status") which fails when
// the `durable` schema is not in the connection's search_path.
// Verified against sea-orm 1.x / sea-query 0.32. If a future sea-orm release
// sanitises Iden strings, this will break — see the assertion test below.
#[sea_orm(
    rs_type = "String",
    db_type = "Enum",
    enum_name = "durable\".\"task_status"
)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
pub enum TaskStatus {
    #[sea_orm(string_value = "PENDING")]
    Pending,
    #[sea_orm(string_value = "RUNNING")]
    Running,
    #[sea_orm(string_value = "COMPLETED")]
    Completed,
    #[sea_orm(string_value = "FAILED")]
    Failed,
    #[sea_orm(string_value = "PAUSED")]
    Paused,
    #[sea_orm(string_value = "CANCELLED")]
    Cancelled,
}

#[cfg(test)]
mod tests {
    use super::*;
    use sea_orm::ActiveEnum;

    /// Guard against sea-orm/sea-query changes that would break the
    /// schema-qualified enum_name hack above.
    #[test]
    fn task_status_iden_is_schema_qualified() {
        let iden = TaskStatus::name();
        let quoted = iden.to_string();
        assert_eq!(
            quoted, r#"durable"."task_status"#,
            "TaskStatus Iden must render as the unquoted schema-qualified name; \
             sea-query wraps it to produce \"durable\".\"task_status\" in SQL"
        );
    }

    /// Verify the actual SQL generated by SeaORM insert/update/select contains
    /// the schema-qualified enum cast, not bare `task_status`.
    #[test]
    fn task_status_sql_is_schema_qualified() {
        use crate::entity::task;
        use sea_orm::{
            ColumnTrait, DbBackend, EntityTrait, IntoActiveModel, QueryFilter, QueryTrait, Set,
        };

        // INSERT
        let model = task::ActiveModel {
            id: Set(uuid::Uuid::nil()),
            name: Set("test".into()),
            kind: Set("WORKFLOW".into()),
            status: Set(TaskStatus::Pending),
            ..Default::default()
        };
        let sql = task::Entity::insert(model)
            .build(DbBackend::Postgres)
            .to_string();
        assert!(
            !sql.contains(r#"AS "task_status""#),
            "INSERT must not contain bare CAST AS \"task_status\": {sql}"
        );

        // SELECT with filter
        let sql = task::Entity::find()
            .filter(task::Column::Status.eq(TaskStatus::Running))
            .build(DbBackend::Postgres)
            .to_string();
        assert!(
            !sql.contains(r#"AS "task_status""#),
            "SELECT must not contain bare CAST AS \"task_status\": {sql}"
        );

        // UPDATE
        let model = task::Model {
            id: uuid::Uuid::nil(),
            parent_id: None,
            sequence: None,
            name: "test".into(),
            status: TaskStatus::Running,
            kind: "WORKFLOW".into(),
            input: None,
            output: None,
            error: None,
            max_retries: 3,
            retry_count: 0,
            cron: None,
            next_run_at: None,
            queue_name: None,
            handler: None,
            executor_id: None,
            app_version: None,
            timeout_ms: None,
            deadline_epoch_ms: None,
            recovery_count: 0,
            max_recovery_attempts: 3,
            created_at: sea_orm::prelude::DateTimeWithTimeZone::default(),
            started_at: None,
            completed_at: None,
        };
        let mut active = model.into_active_model();
        active.status = Set(TaskStatus::Completed);
        let sql = task::Entity::update(active)
            .build(DbBackend::Postgres)
            .to_string();
        assert!(
            !sql.contains(r#"AS "task_status""#),
            "UPDATE must not contain bare CAST AS \"task_status\": {sql}"
        );
    }
}