CommandResultFactory.java

/*-
 * ========================LICENSE_START=================================
 * flyway-core
 * ========================================================================
 * Copyright (C) 2010 - 2025 Red Gate Software Ltd
 * ========================================================================
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * =========================LICENSE_END==================================
 */
package org.flywaydb.core.api.output;

import org.flywaydb.core.api.*;
import org.flywaydb.core.api.configuration.Configuration;
import org.flywaydb.core.extensibility.AppliedMigration;
import org.flywaydb.core.internal.database.base.Database;
import org.flywaydb.core.internal.license.VersionPrinter;

import java.sql.Connection;
import java.util.*;
import java.util.stream.Collectors;

public class CommandResultFactory {
    public static InfoResult createInfoResult(final Configuration configuration,
        final Database database,
        final MigrationInfo[] migrationInfos,
        final MigrationInfo current,
        final boolean allSchemasEmpty) {
        final String databaseName = getDatabaseName(configuration, database);
        return createInfoResult(configuration, databaseName, migrationInfos, current, allSchemasEmpty);
    }

    public static InfoResult createInfoResult(final Configuration configuration,
        final String databaseName,
        MigrationInfo[] migrationInfos,
        final MigrationInfo current,
        final boolean allSchemasEmpty) {
        final String flywayVersion = VersionPrinter.getVersion();
        final Set<MigrationInfo> undoableMigrations = getUndoMigrations(migrationInfos);

        migrationInfos = removeAvailableUndoMigrations(migrationInfos);

        final List<InfoOutput> infoOutputs = new ArrayList<>();
        for (final MigrationInfo migrationInfo : migrationInfos) {
            infoOutputs.add(createInfoOutput(undoableMigrations, migrationInfo));
        }

        final MigrationVersion currentSchemaVersion = current == null ? MigrationVersion.EMPTY : current.getVersion();
        final MigrationVersion schemaVersionToOutput = currentSchemaVersion == null
            ? MigrationVersion.EMPTY
            : currentSchemaVersion;
        final String schemaVersion = schemaVersionToOutput.getVersion();

        return new InfoResult(flywayVersion,
            databaseName,
            schemaVersion,
            String.join(", ", configuration.getSchemas()),
            infoOutputs,
            allSchemasEmpty);
    }

    public static MigrateResult createMigrateResult(String databaseName, String databaseType, Configuration configuration) {
        String flywayVersion = VersionPrinter.getVersion();
        return new MigrateResult(flywayVersion, databaseName, String.join(", ", configuration.getSchemas()), databaseType);
    }

    public static CleanResult createCleanResult(String databaseName) {
        String flywayVersion = VersionPrinter.getVersion();
        return new CleanResult(flywayVersion, databaseName);
    }

    public static BaselineResult createBaselineResult(String databaseName) {
        String flywayVersion = VersionPrinter.getVersion();
        return new BaselineResult(flywayVersion, databaseName);
    }

    public static ValidateResult createValidateResult(String databaseName, ErrorDetails validationError, int validationCount, List<ValidateOutput> invalidMigrations, List<String> warnings) {
        String flywayVersion = VersionPrinter.getVersion();
        boolean validationSuccessful = validationError == null;
        List<ValidateOutput> invalidMigrationsList = invalidMigrations == null ? new ArrayList<>() : invalidMigrations;

        return new ValidateResult(flywayVersion, databaseName, validationError, validationSuccessful, validationCount, invalidMigrationsList, warnings);
    }

    public static RepairResult createRepairResult(String databaseName) {
        String flywayVersion = VersionPrinter.getVersion();
        return new RepairResult(flywayVersion, databaseName);
    }

    public static InfoOutput createInfoOutput(Set<MigrationInfo> undoableMigrations, MigrationInfo migrationInfo) {
        return new InfoOutput(getCategory(migrationInfo),
                              migrationInfo.getVersion() != null ? migrationInfo.getVersion().getVersion() : "",
                              migrationInfo.getVersion() != null ? migrationInfo.getVersion().getRawVersion() : "",
                              migrationInfo.getDescription(),
                              migrationInfo.getType() != null ? migrationInfo.getType().toString() : "",
                              migrationInfo.getInstalledOn() != null ? migrationInfo.getInstalledOn().toInstant().toString() : "",
                              migrationInfo.getState().getDisplayName(),
                              getUndoableStatus(migrationInfo, undoableMigrations),
                              migrationInfo.getPhysicalLocation() != null ? migrationInfo.getPhysicalLocation() : "",
                              getUndoablePath(migrationInfo, undoableMigrations),
                              migrationInfo.getInstalledBy() != null ? migrationInfo.getInstalledBy() : "",
                              migrationInfo.getShouldExecuteExpression(),
                              migrationInfo.getExecutionTime() != null ? migrationInfo.getExecutionTime() : 0);
    }

    public static MigrateOutput createMigrateOutput(MigrationInfo migrationInfo, int executionTime) {
        return createMigrateOutput(migrationInfo, executionTime, false);
    }

    public static MigrateOutput createMigrateOutput(MigrationInfo migrationInfo, int executionTime, Boolean rolledBack) {
        return new MigrateOutput(getCategory(migrationInfo),
                                 migrationInfo.getVersion() != null ? migrationInfo.getVersion().getVersion() : "",
                                 migrationInfo.getDescription(),
                                 migrationInfo.getType() != null ? migrationInfo.getType().toString() : "",
                                 migrationInfo.getPhysicalLocation() != null ? migrationInfo.getPhysicalLocation() : "",
                                 executionTime,
                                 rolledBack);
    }

    public static ValidateOutput createValidateOutput(MigrationInfo migrationInfo, ErrorDetails validateError) {
        return new ValidateOutput(
                migrationInfo.getVersion() != null ? migrationInfo.getVersion().getVersion() : "",
                migrationInfo.getDescription(),
                migrationInfo.getPhysicalLocation() != null ? migrationInfo.getPhysicalLocation() : "",
                validateError);
    }

    public static RepairOutput createRepairOutput(MigrationInfo migrationInfo) {
        return new RepairOutput(
                migrationInfo.getVersion() != null ? migrationInfo.getVersion().getVersion() : "",
                migrationInfo.getDescription(),
                migrationInfo.getPhysicalLocation() != null ? migrationInfo.getPhysicalLocation() : "");
    }

    public static RepairOutput createRepairOutput(AppliedMigration am) {
        return new RepairOutput(am.getVersion() != null ? am.getVersion().getVersion() : "", am.getDescription(), "");
    }

    private static String getUndoableStatus(MigrationInfo migrationInfo, Set<MigrationInfo> undoableMigrations) {
        if (migrationInfo.getVersion() != null && !migrationInfo.getType().isUndo() && !migrationInfo.getState().equals(MigrationState.UNDONE)) {
            if (!migrationInfo.getState().isFailed() && undoableMigrations.stream().anyMatch(u -> u.getVersion().equals(migrationInfo.getVersion()))) {
                return "Yes";
            }
            return "No";
        }
        return "";
    }

    private static String getUndoablePath(MigrationInfo migrationInfo, Set<MigrationInfo> undoableMigrations) {
        if (migrationInfo.getVersion() != null && !migrationInfo.getType().isUndo() && !migrationInfo.getState().equals(MigrationState.UNDONE)) {
            if (!migrationInfo.getState().isFailed()) {
                return undoableMigrations.stream()
                        .filter(u -> u.getVersion().equals(migrationInfo.getVersion()))
                        .findFirst()
                        .map(MigrationInfo::getPhysicalLocation)
                        .orElse("");
            }
        }
        return "";
    }

    private static Set<MigrationInfo> getUndoMigrations(MigrationInfo[] migrationInfos) {
        return Arrays.stream(migrationInfos)
                .filter(m -> m.getType().isUndo())
                .collect(Collectors.toSet());
    }

    private static MigrationInfo[] removeAvailableUndoMigrations(MigrationInfo[] migrationInfos) {
        return Arrays.stream(migrationInfos)
                .filter(m -> !m.getState().equals(MigrationState.AVAILABLE))
                .toArray(MigrationInfo[]::new);
    }

    private static String getDatabaseName(Configuration configuration, Database database) {
        try {
            return database.getCatalog();
        } catch (Exception e) {
            try (Connection connection = configuration.getDataSource().getConnection()) {
                String catalog = connection.getCatalog();
                return catalog != null ? catalog : "";
            } catch (Exception e1) {
                return "";
            }
        }
    }

    private static String getCategory(MigrationInfo migrationInfo) {
        if (migrationInfo.getType().isSynthetic()) {
            return "";
        }
        if (migrationInfo.getVersion() == null) {
            return "Repeatable";
        }
        if (migrationInfo.getType().isUndo()) {
            return "Undo";
        }
        if (migrationInfo.getType().isBaseline()) {
            return "Baseline";
        }
        return "Versioned";
    }
}