MigrationInfoDumper.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.internal.info;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.flywaydb.core.api.CoreMigrationType;
import org.flywaydb.core.api.MigrationInfo;
import org.flywaydb.core.api.MigrationState;
import org.flywaydb.core.api.MigrationVersion;
import org.flywaydb.core.internal.util.AsciiTable;
import org.flywaydb.core.internal.util.DateUtils;

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

/**
 * Dumps migrations in an ascii-art table in the logs and the console.
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MigrationInfoDumper {

    /**
     * Dumps the info about all migrations into an ascii table.
     *
     * @param migrationInfos The list of migrationInfos to dump.
     * @return The ascii table, as one big multi-line string.
     */
    public static String dumpToAsciiTable(MigrationInfo[] migrationInfos) {
        Set<MigrationVersion> undoableVersions = getUndoableVersions(migrationInfos);
        migrationInfos = removeUndos(migrationInfos);

        List<String> columns = Arrays.asList("Category", "Version", "Description", "Type", "Installed On", "State", "Undoable");

        List<List<String>> rows = new ArrayList<>();
        for (MigrationInfo migrationInfo : migrationInfos) {
            List<String> row = Arrays.asList(
                    getCategory(migrationInfo),
                    getVersionStr(migrationInfo),
                    migrationInfo.getDescription(),
                    migrationInfo.getType().name(),
                    DateUtils.formatDateAsIsoString(migrationInfo.getInstalledOn()),
                    migrationInfo.getState().getDisplayName(),
                    getUndoableStatus(migrationInfo, undoableVersions));
            rows.add(row);
        }

        return new AsciiTable(columns, rows, true, "", "No migrations found").render();
    }

    /**
     * Dumps the info about all migrations into a String of Migration Ids.
     *
     * @param migrationInfos The list of migrationInfos to dump.
     * @return The String containing Migration Ids, separated by comma.
     */
    public static String dumpToMigrationIds(MigrationInfo[] migrationInfos) {
        migrationInfos = removeUndos(migrationInfos);

        return Arrays.stream(migrationInfos)
                     .map(m -> m.getVersion() == null ? m.getDescription() : m.getVersion().getVersion())
                     .collect(Collectors.joining(","));
    }

    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";
    }

    private static String getVersionStr(MigrationInfo migrationInfo) {
        return migrationInfo.getVersion() == null ? "" : migrationInfo.getVersion().toString();
    }

    private static String getUndoableStatus(MigrationInfo migrationInfo, Set<MigrationVersion> undoableVersions) {
        if (migrationInfo.getVersion() != null
                && !migrationInfo.getType().equals(CoreMigrationType.DELETE)
                && !migrationInfo.getState().equals(MigrationState.DELETED)
                && !migrationInfo.getType().isUndo()
                && !migrationInfo.getState().equals(MigrationState.UNDONE)) {
            if (!migrationInfo.getState().isFailed() && undoableVersions.contains(migrationInfo.getVersion())) {
                return "Yes";
            }
            return "No";
        }
        return "";
    }

    private static Set<MigrationVersion> getUndoableVersions(MigrationInfo[] migrationInfos) {
        Set<MigrationVersion> result = new HashSet<>();
        for (MigrationInfo migrationInfo : migrationInfos) {
            if (migrationInfo.getType().isUndo()) {
                result.add(migrationInfo.getVersion());
            }
        }
        return result;
    }

    private static MigrationInfo[] removeUndos(MigrationInfo[] migrationInfos) {
        List<MigrationInfo> result = new ArrayList<>();
        for (MigrationInfo migrationInfo : migrationInfos) {
            if (!migrationInfo.getType().isUndo()) {
                result.add(migrationInfo);
            }
        }
        return result.toArray(new MigrationInfo[0]);
    }
}