//! SQL-facing backend-local build-job metadata.

mod backend_identity;
mod build_job_types;
mod build_job_validation;

use pgrx::{PgRelation, prelude::*, spi};

#[cfg(any(test, feature = "pg_test"))]
use std::sync::atomic::{AtomicU8, Ordering};

use crate::domain_types::{ArtifactKind, artifact_kind_from_catalog, artifact_kind_from_sql};
use crate::error::raise_sql_error;
use crate::pgcontext::BuildJobStatus;
use backend_identity::{backend_is_active, current_backend_identity};
use build_job_types::{ActiveBuildJobCandidate, BuildJobRow};
use build_job_validation::{
    build_generation_kind, build_status_from_catalog, ensure_build_runner_supported,
    parse_build_status_command, validate_non_empty_text, validate_non_negative_units,
    validate_positive_units,
};

use crate::build_worker;

type BuildJobResult = (
    i64,
    String,
    String,
    String,
    String,
    BuildJobStatus,
    Option<i32>,
    i32,
    i64,
    i64,
    bool,
    Option<String>,
);

#[cfg(any(test, feature = "pg_test"))]
static BUILD_JOB_FAILPOINT: AtomicU8 = AtomicU8::new(0);

#[cfg(feature = "pg_test")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BuildJobFailpoint {
    Checkpoint = 1,
    SourceRead = 2,
    Cancel = 3,
    BackendLossRecovery = 4,
    Retry = 5,
    SchemaDriftValidation = 6,
}

fn build_job_failpoint(stage: u8, label: &'static str) {
    #[cfg(any(test, feature = "pg_test"))]
    if BUILD_JOB_FAILPOINT.load(Ordering::SeqCst) == stage {
        raise_sql_error(
            PgSqlErrorCode::ERRCODE_INTERNAL_ERROR,
            format!("injected build job failpoint: {label}"),
        );
    }
    let _ = (stage, label);
}

#[cfg(feature = "pg_test")]
#[pg_extern]
fn test_set_build_job_failpoint(name: Option<String>) {
    let failpoint = match name.as_deref() {
        None => 0,
        Some("before_checkpoint") => BuildJobFailpoint::Checkpoint as u8,
        Some("before_source_read") => BuildJobFailpoint::SourceRead as u8,
        Some("before_cancel") => BuildJobFailpoint::Cancel as u8,
        Some("before_backend_loss_recovery") => BuildJobFailpoint::BackendLossRecovery as u8,
        Some("before_retry") => BuildJobFailpoint::Retry as u8,
        Some("before_schema_drift_validation") => BuildJobFailpoint::SchemaDriftValidation as u8,
        Some(value) => raise_sql_error(
            PgSqlErrorCode::ERRCODE_INVALID_PARAMETER_VALUE,
            format!("unknown build job failpoint: {value}"),
        ),
    };
    BUILD_JOB_FAILPOINT.store(failpoint, Ordering::SeqCst);
}

/// Starts backend-local metadata for a pgContext artifact or projection.
///
/// This function does not make native PostgreSQL `CREATE INDEX` resumable and
/// does not spawn a Rust worker. It records ownership, progress budget, and the
/// current PostgreSQL backend identity for a pgContext-owned generation so the
/// caller can execute it synchronously or retry it from another backend after
/// failure.
///
/// # Errors
///
/// Raises `undefined_object` when `collection` does not exist. Raises
/// `insufficient_privilege` when the caller is not a collection owner member.
/// Raises `invalid_parameter_value` for unsupported build kinds, empty names,
/// or negative totals.
#[allow(
    clippy::type_complexity,
    reason = "pgrx SQL generation requires the explicit table row tuple"
)]
#[pg_extern(name = "start_build_job", security_definer)]
#[search_path(pg_catalog, pgcontext)]
pub fn start_build_job(
    collection: String,
    artifact_kind: String,
    artifact_name: String,
    target_name: String,
    total_units: i64,
) -> TableIterator<
    'static,
    (
        name!(build_job_id, i64),
        name!(collection_name, String),
        name!(artifact_kind, String),
        name!(artifact_name, String),
        name!(target_name, String),
        name!(status, BuildJobStatus),
        name!(backend_pid, Option<i32>),
        name!(attempt, i32),
        name!(processed_units, i64),
        name!(total_units, i64),
        name!(cancel_requested, bool),
        name!(error_message, Option<String>),
    ),
> {
    let artifact_kind = artifact_kind_from_sql(&artifact_kind);
    let _generation_kind = build_generation_kind(artifact_kind);
    validate_non_empty_text(&artifact_name, "artifact_name");
    validate_non_empty_text(&target_name, "target_name");
    validate_non_negative_units(total_units, "total_units");

    let collection_id = resolve_owned_collection_id(&collection);
    let row = insert_build_job(
        collection_id,
        artifact_kind,
        &artifact_name,
        &target_name,
        total_units,
    );
    TableIterator::once(build_job_result(row))
}

/// Enqueues a durable generation job for PostgreSQL-supervised execution.
///
/// The row is created as ownerless `planned` work and is invisible to workers
/// until the caller commits. If the enqueue transaction outlives the launcher's
/// idle window, or if worker capacity is unavailable, a later
/// [`wake_build_jobs`] call gives the committed job another launch opportunity.
/// Exact retrieval remains available throughout.
///
/// # Errors
///
/// Raises collection ownership errors, rejects unsafe publication aliases, and
/// fails closed for job kinds without a registered executor. P2 registers only
/// `certification`; later phases add their real artifact executors.
#[allow(
    clippy::type_complexity,
    reason = "pgrx SQL generation requires the explicit table row tuple"
)]
#[pg_extern(name = "enqueue_build_job", security_definer)]
#[search_path(pg_catalog, pgcontext)]
pub fn enqueue_build_job(
    collection: String,
    job_kind: String,
    publication_alias: String,
) -> TableIterator<
    'static,
    (
        name!(build_job_id, i64),
        name!(collection_name, String),
        name!(artifact_kind, String),
        name!(artifact_name, String),
        name!(target_name, String),
        name!(status, BuildJobStatus),
        name!(backend_pid, Option<i32>),
        name!(attempt, i32),
        name!(processed_units, i64),
        name!(total_units, i64),
        name!(cancel_requested, bool),
        name!(error_message, Option<String>),
    ),
> {
    let artifact_kind = artifact_kind_from_sql(&job_kind);
    if artifact_kind != ArtifactKind::Certification {
        raise_sql_error(
            PgSqlErrorCode::ERRCODE_FEATURE_NOT_SUPPORTED,
            format!("no supervised executor is registered for job kind: {job_kind}"),
        );
    }
    context_build::PublicationAlias::new(publication_alias.clone()).unwrap_or_else(|_| {
        raise_sql_error(
            PgSqlErrorCode::ERRCODE_INVALID_PARAMETER_VALUE,
            "publication_alias must be non-empty, bounded, and contain no control characters",
        )
    });
    let collection_id = resolve_owned_collection_id(&collection);
    let row = insert_supervised_build_job(
        collection_id,
        ArtifactKind::Certification,
        context_build::BuildJobKind::Certification,
        &publication_alias,
        "pgcontext._collection_points",
        None,
    );
    let _worker_started = build_worker::launch_for_current_database();
    TableIterator::once(build_job_result(row))
}

/// Enqueues one bounded HNSW segment-pair compaction for supervised execution.
///
/// The index must belong to the collection's authoritative source table. The
/// durable target is its relation OID, and the worker revalidates that binding
/// before touching the index. Each execution compacts at most one adjacent
/// immutable pair, so neither foreground nor background work grows with the
/// complete index.
#[allow(
    clippy::type_complexity,
    reason = "pgrx SQL generation requires the explicit table row tuple"
)]
#[pg_extern(name = "enqueue_hnsw_compaction", security_definer)]
#[search_path(pg_catalog, pgcontext)]
pub fn enqueue_hnsw_compaction(
    collection: String,
    index: PgRelation,
) -> TableIterator<
    'static,
    (
        name!(build_job_id, i64),
        name!(collection_name, String),
        name!(artifact_kind, String),
        name!(artifact_name, String),
        name!(target_name, String),
        name!(status, BuildJobStatus),
        name!(backend_pid, Option<i32>),
        name!(attempt, i32),
        name!(processed_units, i64),
        name!(total_units, i64),
        name!(cancel_requested, bool),
        name!(error_message, Option<String>),
    ),
> {
    let collection_id = resolve_owned_collection_id(&collection);
    let (index_oid, index_name) = resolve_collection_hnsw_index(collection_id, &index);
    let directory_epoch = crate::hnsw_am::hnsw_directory_epoch(index.as_ptr());
    let target_name = index_oid.to_string();
    let row = insert_supervised_build_job(
        collection_id,
        ArtifactKind::Index,
        context_build::BuildJobKind::Compaction,
        &index_name,
        &target_name,
        Some(directory_epoch),
    );
    let _worker_started = build_worker::launch_for_current_database();
    TableIterator::once(build_job_result(row))
}

/// Enqueues one supervised IVFFlat delta-fold/retraining generation.
#[allow(
    clippy::type_complexity,
    reason = "pgrx SQL generation requires the explicit table row tuple"
)]
#[pg_extern(name = "enqueue_ivfflat_compaction", security_definer)]
#[search_path(pg_catalog, pgcontext)]
pub fn enqueue_ivfflat_compaction(
    collection: String,
    index: PgRelation,
) -> TableIterator<
    'static,
    (
        name!(build_job_id, i64),
        name!(collection_name, String),
        name!(artifact_kind, String),
        name!(artifact_name, String),
        name!(target_name, String),
        name!(status, BuildJobStatus),
        name!(backend_pid, Option<i32>),
        name!(attempt, i32),
        name!(processed_units, i64),
        name!(total_units, i64),
        name!(cancel_requested, bool),
        name!(error_message, Option<String>),
    ),
> {
    let collection_id = resolve_owned_collection_id(&collection);
    let (index_oid, index_name) = resolve_collection_ivfflat_index(collection_id, &index);
    let generation = crate::ivfflat_am::ivfflat_generation(index.as_ptr());
    let target_name = index_oid.to_string();
    let row = insert_supervised_build_job(
        collection_id,
        ArtifactKind::Index,
        context_build::BuildJobKind::Compaction,
        &index_name,
        &target_name,
        Some(generation),
    );
    let _worker_started = build_worker::launch_for_current_database();
    TableIterator::once(build_job_result(row))
}

#[pg_extern(name = "_enqueue_ivfflat_compaction_debt", security_definer)]
#[search_path(pg_catalog, pgcontext)]
fn enqueue_ivfflat_compaction_debt(index_oid: pg_sys::Oid, generation: i64) -> bool {
    if generation <= 0 {
        return false;
    }
    Spi::get_one_with_args::<bool>(
        "WITH target AS MATERIALIZED (
             SELECT collections.collection_id,
                    class.oid::regclass::text AS artifact_name,
                    class.oid::text AS target_name
               FROM pgcontext._collections AS collections
               JOIN pg_catalog.pg_index AS catalog_index
                 ON catalog_index.indrelid = collections.source_table_oid
               JOIN pg_catalog.pg_class AS class ON class.oid = catalog_index.indexrelid
               JOIN pg_catalog.pg_am AS access_method ON access_method.oid = class.relam
              WHERE class.oid = $1
                AND access_method.amname = 'pgcontext_ivfflat'
              ORDER BY collections.collection_id
              LIMIT 1
         )
         INSERT INTO pgcontext._build_jobs (
                    collection_id, artifact_kind, artifact_name, target_name,
                    job_kind, status, total_units, config_revision, supervised
         )
         SELECT collection_id, 'index', artifact_name, target_name,
                'compaction', 'planned', 0, $2, true
           FROM target
         ON CONFLICT (collection_id, artifact_kind, artifact_name, target_name)
             WHERE status IN ('planned', 'running', 'cancel_requested', 'validating', 'publishing')
         DO NOTHING
         RETURNING true",
        &[index_oid.into(), generation.into()],
    )
    .ok()
    .flatten()
    .unwrap_or(false)
}

/// Best-effort post-commit wake-up for ownerless supervised work.
///
/// A planned job is durable even when worker capacity is temporarily
/// unavailable. Calling this function from a later transaction is idempotent
/// and gives already-committed work another launch opportunity.
///
/// # Errors
///
/// Raises collection visibility and ownership errors for `collection`.
#[pg_extern(name = "wake_build_jobs", security_definer)]
#[search_path(pg_catalog, pgcontext)]
pub fn wake_build_jobs(collection: String) -> bool {
    let _collection_id = resolve_owned_collection_id(&collection);
    build_worker::launch_for_current_database()
}

#[cfg(feature = "pg_test")]
#[pg_extern]
fn test_run_build_worker_step() -> bool {
    build_worker::process_one_step()
}

#[cfg(feature = "pg_test")]
#[pg_extern]
fn test_stale_build_worker_attempt_is_fenced(build_job_id: i64) -> bool {
    build_worker::stale_attempt_is_fenced_for_test(build_job_id)
}

/// Lists build jobs for a collection owned by the caller.
///
/// Running jobs whose backend identity no longer appears in `pg_stat_activity`
/// are reported as [`BuildJobStatus::Abandoned`] so retry decisions do not
/// depend on in-process Rust state.
///
/// # Errors
///
/// Raises `undefined_object` when `collection` does not exist. Raises
/// `insufficient_privilege` when the caller is not a collection owner member.
#[allow(
    clippy::type_complexity,
    reason = "pgrx SQL generation requires the explicit table row tuple"
)]
#[pg_extern(name = "build_jobs", security_definer)]
#[search_path(pg_catalog, pgcontext)]
pub fn build_jobs(
    collection: String,
) -> TableIterator<
    'static,
    (
        name!(build_job_id, i64),
        name!(collection_name, String),
        name!(artifact_kind, String),
        name!(artifact_name, String),
        name!(target_name, String),
        name!(status, BuildJobStatus),
        name!(backend_pid, Option<i32>),
        name!(attempt, i32),
        name!(processed_units, i64),
        name!(total_units, i64),
        name!(cancel_requested, bool),
        name!(error_message, Option<String>),
    ),
> {
    let collection_id = resolve_owned_collection_id(&collection);
    TableIterator::new(
        select_build_jobs(collection_id)
            .into_iter()
            .map(build_job_result),
    )
}

/// Updates backend-local build progress.
///
/// The currently connected PostgreSQL backend must own a running or
/// cancel-requested job before it can update progress. Terminal statuses clear
/// backend ownership and set `completed_at`.
///
/// # Errors
///
/// Raises `undefined_object` when `build_job_id` does not exist. Raises
/// `insufficient_privilege` when the caller is not a collection owner member.
/// Raises `object_not_in_prerequisite_state` when another active backend owns
/// the job or the transition is invalid. Raises `invalid_parameter_value` for
/// invalid status values, incomplete completion, backward progress, or progress
/// beyond `total_units`.
#[allow(
    clippy::type_complexity,
    reason = "pgrx SQL generation requires the explicit table row tuple"
)]
#[pg_extern(name = "update_build_job", security_definer)]
#[search_path(pg_catalog, pgcontext)]
pub fn update_build_job(
    build_job_id: i64,
    processed_units: i64,
    status: String,
    error_message: default!(Option<String>, "NULL"),
) -> TableIterator<
    'static,
    (
        name!(build_job_id, i64),
        name!(collection_name, String),
        name!(artifact_kind, String),
        name!(artifact_name, String),
        name!(target_name, String),
        name!(status, BuildJobStatus),
        name!(backend_pid, Option<i32>),
        name!(attempt, i32),
        name!(processed_units, i64),
        name!(total_units, i64),
        name!(cancel_requested, bool),
        name!(error_message, Option<String>),
    ),
> {
    validate_non_negative_units(processed_units, "processed_units");
    let next_status = parse_build_status_command(&status);
    let current = resolve_visible_build_job_for_update(build_job_id);
    ensure_current_backend_can_update(&current);
    if processed_units > current.total_units {
        raise_sql_error(
            PgSqlErrorCode::ERRCODE_INVALID_PARAMETER_VALUE,
            format!(
                "build job progress exceeds total: {processed_units} > {}",
                current.total_units
            ),
        );
    }
    if processed_units < current.processed_units {
        raise_sql_error(
            PgSqlErrorCode::ERRCODE_INVALID_PARAMETER_VALUE,
            format!(
                "build job progress cannot go backwards: {processed_units} < {}",
                current.processed_units
            ),
        );
    }
    if next_status == BuildJobStatus::Completed && processed_units != current.total_units {
        raise_sql_error(
            PgSqlErrorCode::ERRCODE_INVALID_PARAMETER_VALUE,
            format!(
                "completed build job progress must equal total: {processed_units} <> {}",
                current.total_units
            ),
        );
    }
    let row = update_build_job_row(
        build_job_id,
        processed_units,
        next_status,
        error_message,
        None,
    );
    TableIterator::once(build_job_result(row))
}

/// Requests cancellation for a visible running build job.
///
/// Cancellation is cooperative: the owning backend observes the request through
/// `build_jobs` or the next `update_build_job` call and records `cancelled`
/// after cleaning up.
///
/// # Errors
///
/// Raises `undefined_object` when `build_job_id` does not exist. Raises
/// `insufficient_privilege` when the caller is not a collection owner member.
/// Raises `object_not_in_prerequisite_state` for terminal jobs.
#[allow(
    clippy::type_complexity,
    reason = "pgrx SQL generation requires the explicit table row tuple"
)]
#[pg_extern(name = "request_build_cancel", security_definer)]
#[search_path(pg_catalog, pgcontext)]
pub fn request_build_cancel(
    build_job_id: i64,
) -> TableIterator<
    'static,
    (
        name!(build_job_id, i64),
        name!(collection_name, String),
        name!(artifact_kind, String),
        name!(artifact_name, String),
        name!(target_name, String),
        name!(status, BuildJobStatus),
        name!(backend_pid, Option<i32>),
        name!(attempt, i32),
        name!(processed_units, i64),
        name!(total_units, i64),
        name!(cancel_requested, bool),
        name!(error_message, Option<String>),
    ),
> {
    let current = resolve_visible_build_job_for_update(build_job_id);
    match effective_build_status(&current) {
        BuildJobStatus::Planned => {
            TableIterator::once(build_job_result(cancel_planned_build_job_row(build_job_id)))
        }
        BuildJobStatus::Running | BuildJobStatus::Validating => {
            TableIterator::once(build_job_result(request_build_cancel_row(build_job_id)))
        }
        BuildJobStatus::CancelRequested => TableIterator::once(build_job_result(current)),
        status => raise_sql_error(
            PgSqlErrorCode::ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE,
            format!("cannot cancel build job {build_job_id} in status {status:?}"),
        ),
    }
}

/// Retries a failed, cancelled, or abandoned build job in the current backend.
///
/// # Errors
///
/// Raises `undefined_object` when `build_job_id` does not exist. Raises
/// `insufficient_privilege` when the caller is not a collection owner member.
/// Raises `object_not_in_prerequisite_state` when the job is still actively
/// running or already completed.
#[allow(
    clippy::type_complexity,
    reason = "pgrx SQL generation requires the explicit table row tuple"
)]
#[pg_extern(name = "retry_build_job", security_definer)]
#[search_path(pg_catalog, pgcontext)]
pub fn retry_build_job(
    build_job_id: i64,
) -> TableIterator<
    'static,
    (
        name!(build_job_id, i64),
        name!(collection_name, String),
        name!(artifact_kind, String),
        name!(artifact_name, String),
        name!(target_name, String),
        name!(status, BuildJobStatus),
        name!(backend_pid, Option<i32>),
        name!(attempt, i32),
        name!(processed_units, i64),
        name!(total_units, i64),
        name!(cancel_requested, bool),
        name!(error_message, Option<String>),
    ),
> {
    let current = resolve_visible_build_job_for_update(build_job_id);
    match effective_build_status(&current) {
        BuildJobStatus::Failed | BuildJobStatus::Cancelled | BuildJobStatus::Abandoned => {
            build_job_failpoint(5, "before_retry");
            let supervised = Spi::get_one_with_args::<bool>(
                "SELECT supervised FROM pgcontext._build_jobs WHERE build_job_id = $1",
                &[build_job_id.into()],
            )
            .unwrap_or_else(|error| {
                raise_sql_error(
                    PgSqlErrorCode::ERRCODE_INTERNAL_ERROR,
                    format!("build job executor lookup failed: {error}"),
                )
            })
            .unwrap_or(false);
            let row = if supervised {
                let row = retry_supervised_build_job_row(
                    build_job_id,
                    current.stored_status,
                    current.attempt,
                );
                let _worker_started = build_worker::launch_for_current_database();
                row
            } else {
                retry_build_job_row(build_job_id)
            };
            TableIterator::once(build_job_result(row))
        }
        status => raise_sql_error(
            PgSqlErrorCode::ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE,
            format!("cannot retry build job {build_job_id} in status {status:?}"),
        ),
    }
}

/// Runs a backend-local build job synchronously in the current backend.
///
/// This runner is intentionally narrow: it advances experimental `segment` and
/// `mmap` artifact jobs through the existing progress table without spawning
/// workers or sharing Rust heap state across PostgreSQL backends. Later
/// artifact implementations can replace the simulated unit loop with real
/// source-table rebuild steps while keeping the same ownership, retry, and
/// crash-recovery contract. Cancellation observed by this function is limited
/// to requests already visible before a runner step starts; a concurrent
/// `request_build_cancel` may wait for this SQL call's transaction to release
/// its row lock.
///
/// # Errors
///
/// Raises `undefined_object` when `build_job_id` does not exist. Raises
/// `insufficient_privilege` when the caller is not a collection owner member.
/// Raises `object_not_in_prerequisite_state` for terminal jobs, jobs owned by
/// another active backend, or artifact kinds that do not have a local runner
/// yet. Raises `invalid_parameter_value` when `units_per_step` is not positive.
#[allow(
    clippy::type_complexity,
    reason = "pgrx SQL generation requires the explicit table row tuple"
)]
#[pg_extern(name = "run_build_job", security_definer)]
#[search_path(pg_catalog, pgcontext)]
pub fn run_build_job(
    build_job_id: i64,
    units_per_step: default!(i64, "1"),
) -> TableIterator<
    'static,
    (
        name!(build_job_id, i64),
        name!(collection_name, String),
        name!(artifact_kind, String),
        name!(artifact_name, String),
        name!(target_name, String),
        name!(status, BuildJobStatus),
        name!(backend_pid, Option<i32>),
        name!(attempt, i32),
        name!(processed_units, i64),
        name!(total_units, i64),
        name!(cancel_requested, bool),
        name!(error_message, Option<String>),
    ),
> {
    validate_positive_units(units_per_step, "units_per_step");

    let current = resolve_visible_build_job(build_job_id);
    ensure_build_runner_supported(current.artifact_kind);
    ensure_current_backend_can_update(&current);

    if matches!(
        effective_build_status(&current),
        BuildJobStatus::CancelRequested
    ) || current.cancel_requested
    {
        build_job_failpoint(3, "before_cancel");
        let row = update_build_job_row(
            build_job_id,
            current.processed_units,
            BuildJobStatus::Cancelled,
            Some("build job cancelled before runner step".to_owned()),
            None,
        );
        return TableIterator::once(build_job_result(row));
    }

    if current.processed_units == current.total_units {
        let row = update_build_job_row(
            build_job_id,
            current.total_units,
            BuildJobStatus::Completed,
            None,
            None,
        );
        return TableIterator::once(build_job_result(row));
    }

    build_job_failpoint(6, "before_schema_drift_validation");
    build_job_failpoint(2, "before_source_read");
    let (last_source_point_id, scanned_units) =
        scan_source_point_batch(build_job_id, current.last_source_point_id, units_per_step);
    replay_build_deltas(build_job_id);
    if scanned_units == 0 {
        raise_sql_error(
            PgSqlErrorCode::ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE,
            format!(
                "build source exhausted before declared total: {} < {}",
                current.processed_units, current.total_units
            ),
        );
    }
    let next_processed = current.processed_units + scanned_units;
    if next_processed > current.total_units {
        raise_sql_error(
            PgSqlErrorCode::ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE,
            format!(
                "build source batch exceeds declared total: {next_processed} > {}",
                current.total_units
            ),
        );
    }
    let next_status = if next_processed == current.total_units {
        BuildJobStatus::Completed
    } else {
        BuildJobStatus::Running
    };
    build_job_failpoint(1, "before_checkpoint");
    let row = update_build_job_row(
        build_job_id,
        next_processed,
        next_status,
        None,
        Some(last_source_point_id),
    );
    TableIterator::once(build_job_result(row))
}

include!("build_jobs/persistence.rs");
