waypoint-core 0.8.1

Lightweight, Flyway-compatible SQL migration library for PostgreSQL and MySQL
Documentation
//! Compare live database schema against a target and generate migration SQL.

use serde::Serialize;

#[cfg(feature = "postgres")]
use tokio_postgres::Client;

use crate::config::WaypointConfig;
use crate::db::DbClient;
use crate::dialect::DialectKind;
use crate::error::Result;
use crate::schema::{self, SchemaDiff};

/// Target to compare the current schema against.
pub enum DiffTarget {
    /// Compare against another database identified by its connection URL.
    Database(String),
}

/// Report produced by the diff command.
#[derive(Debug, Serialize)]
pub struct DiffReport {
    /// List of individual schema differences found.
    pub diffs: Vec<SchemaDiff>,
    /// DDL SQL statements generated to reconcile the differences.
    pub generated_sql: String,
    /// Whether any differences were detected.
    pub has_changes: bool,
}

/// Execute the diff command (PostgreSQL legacy entry).
#[cfg(feature = "postgres")]
#[deprecated(
    since = "0.6.0",
    note = "Unused PostgreSQL-only entry point superseded by `execute_db`, which handles both engines. Will be removed in 1.0."
)]
pub async fn execute(
    client: &Client,
    config: &WaypointConfig,
    target: DiffTarget,
) -> Result<DiffReport> {
    let schema_name = &config.migrations.schema;

    let current = schema::introspect(client, schema_name).await?;

    let target_snapshot = match target {
        DiffTarget::Database(ref url) => {
            let target_client =
                crate::db::connect_with_transport(url, &crate::db::TransportConfig::default())
                    .await?;
            schema::introspect(&target_client, schema_name).await?
        }
    };

    let diffs = schema::diff(&current, &target_snapshot);
    let generated_sql = schema::generate_ddl(&diffs);
    let has_changes = !diffs.is_empty();

    Ok(DiffReport {
        diffs,
        generated_sql,
        has_changes,
    })
}

/// Execute the diff command (dialect-aware entry).
///
/// Generated SQL is PostgreSQL-flavored when comparing PG schemas. On MySQL
/// the structural `diffs` list is populated correctly but `generated_sql` is
/// best-effort PG-shaped — consume the structured diffs for MySQL until a
/// MySQL DDL generator lands.
pub async fn execute_db(
    client: &DbClient,
    config: &WaypointConfig,
    target: DiffTarget,
) -> Result<DiffReport> {
    let schema_name = client.resolve_schema(&config.migrations.schema).await?;

    let current = schema::introspect_db(client, &schema_name).await?;

    let target_snapshot = match target {
        DiffTarget::Database(ref url) => {
            // The --target-url connection reuses the caller's [database]
            // transport settings (SSL mode, timeouts, keepalive) rather than
            // silently connecting with hardcoded defaults.
            let target_client = crate::db::connect_for_url(url, config).await?;
            // Schema resolution for --target-url differs by engine:
            //   PG: schemas are namespaces *within* a database, so the
            //       configured `schema` (e.g. "public") applies to both sides.
            //   MySQL: "schema" === "database", and the target URL specifies a
            //       different database. We introspect whatever the target
            //       connection actually points at, not the source's configured
            //       db name.
            let target_schema = match target_client.dialect_kind() {
                DialectKind::Mysql => target_client.current_database().await?,
                DialectKind::Postgres => {
                    target_client
                        .resolve_schema(&config.migrations.schema)
                        .await?
                }
            };
            schema::introspect_db(&target_client, &target_schema).await?
        }
    };

    let diffs = schema::diff(&current, &target_snapshot);
    let generated_sql = schema::generate_ddl(&diffs);
    let has_changes = !diffs.is_empty();

    Ok(DiffReport {
        diffs,
        generated_sql,
        has_changes,
    })
}