pgmold 0.33.6

PostgreSQL schema-as-code management tool
Documentation
pub mod report;
pub mod unsupported;

use crate::diff::compute_diff;
use crate::dump::generate_dump;
use crate::parser::parse_sql_string;
use crate::pg::connection::PgConnection;
use crate::pg::introspect::introspect_schema;
use crate::util::{sanitize_url, Result, SchemaError};

pub use report::{generate_json_report, generate_text_report, BaselineReport, ObjectCounts};
pub use unsupported::{detect_unsupported_objects, UnsupportedObject};

#[derive(Debug, Clone)]
pub struct BaselineResult {
    pub sql_dump: String,
    pub report: BaselineReport,
}

pub async fn run_baseline(
    connection: &PgConnection,
    database_url: &str,
    target_schemas: &[String],
    output_path: &str,
) -> Result<BaselineResult> {
    let introspected = introspect_schema(connection, target_schemas, false).await?;

    let header = format!(
        "-- Generated by pgmold baseline\n-- Schemas: {}",
        target_schemas.join(", ")
    );
    let sql_dump = generate_dump(&introspected, Some(&header));

    let parsed = parse_sql_string(&sql_dump).map_err(|e| {
        SchemaError::ValidationError(format!(
            "Round-trip failure: generated SQL could not be parsed back: {e}"
        ))
    })?;

    let introspected_fingerprint = introspected.fingerprint();
    let parsed_fingerprint = parsed.fingerprint();
    let round_trip_ok = introspected_fingerprint == parsed_fingerprint;

    let diff_ops = compute_diff(&introspected, &parsed);
    let zero_diff_ok = diff_ops.is_empty();

    let warnings = detect_unsupported_objects(connection, target_schemas).await?;

    let report = BaselineReport {
        database_url: sanitize_url(database_url),
        target_schemas: target_schemas.to_vec(),
        output_path: output_path.to_string(),
        object_counts: ObjectCounts::from_schema(&introspected),
        round_trip_ok,
        zero_diff_ok,
        fingerprint: introspected_fingerprint,
        warnings,
    };

    Ok(BaselineResult { sql_dump, report })
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::model::Schema;

    #[test]
    fn baseline_result_structure() {
        let report = BaselineReport {
            database_url: "postgres://localhost/test".into(),
            target_schemas: vec!["public".into()],
            output_path: "schema.sql".into(),
            object_counts: ObjectCounts::from_schema(&Schema::default()),
            round_trip_ok: true,
            zero_diff_ok: true,
            fingerprint: "abc123".into(),
            warnings: vec![],
        };

        let result = BaselineResult {
            sql_dump: "-- test".into(),
            report,
        };

        assert!(result.report.is_success());
        assert!(!result.report.has_warnings());
    }

    #[test]
    fn baseline_result_with_warnings() {
        let report = BaselineReport {
            database_url: "postgres://localhost/test".into(),
            target_schemas: vec!["public".into()],
            output_path: "schema.sql".into(),
            object_counts: ObjectCounts::from_schema(&Schema::default()),
            round_trip_ok: true,
            zero_diff_ok: true,
            fingerprint: "abc123".into(),
            warnings: vec![UnsupportedObject::CompositeType {
                schema: "public".into(),
                name: "address".into(),
            }],
        };

        assert!(report.has_warnings());
        assert!(report.is_success());
    }

    #[test]
    fn baseline_result_failure() {
        let report = BaselineReport {
            database_url: "postgres://localhost/test".into(),
            target_schemas: vec!["public".into()],
            output_path: "schema.sql".into(),
            object_counts: ObjectCounts::from_schema(&Schema::default()),
            round_trip_ok: false,
            zero_diff_ok: false,
            fingerprint: "abc123".into(),
            warnings: vec![],
        };

        assert!(!report.is_success());
    }
}