DatabasePlatform.java

package io.github.databaseaudits.platform;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Locale;

import javax.sql.DataSource;

import io.github.databaseaudits.audit.jpa.SchemaEntityValidationAudit;
import io.github.databaseaudits.audit.runtime.UnconditionalMutationAudit;

/**
 * The database product an audit runs against — selects the catalog SQL the
 * audit executes.
 *
 * <p>
 * Every catalog-driven audit supports every platform listed here. The
 * EXPLAIN-driven (plan) runtime audits require {@link #POSTGRESQL} — no other
 * platform offers a parameter-free generic-plan EXPLAIN with planner-penalty
 * settings — and fail fast (rather than pass vacuously) on any other platform.
 * {@link UnconditionalMutationAudit} and {@link SchemaEntityValidationAudit}
 * run no database-specific SQL and need no platform.
 *
 * <p>
 * Pass the platform to each audit's constructor, or detect it once with
 * {@link #fromDataSource(DataSource)} (the Spring module's
 * {@code DatabaseAuditTestConfiguration} does exactly that).
 *
 * <p>
 * To add a platform, add an enum value: every per-audit SQL {@code switch} is
 * exhaustive with no {@code default}, so the compiler then flags each place
 * that needs SQL for the new platform.
 */
public enum DatabasePlatform {
    /** H2 2.x (the 1.x information_schema had a different layout). */
    H2,

    /**
     * MariaDB 10.6+. (Connecting to MariaDB through MySQL Connector/J detects
     * as {@link #MYSQL} — same SQL.)
     */
    MARIADB,

    /** MySQL 8+. Aurora MySQL reports as MySQL. */
    MYSQL,

    /**
     * PostgreSQL 11+ for the catalog audits, 16+ for the plan audits. Aurora
     * PostgreSQL reports as PostgreSQL.
     */
    POSTGRESQL;

    private static final String FAILED_OBTAINING_DB_FROM_DATA_SOURCE_MSG =
            "Could not read the database product name from the DataSource";

    /**
     * Detects the platform from
     * {@link java.sql.DatabaseMetaData#getDatabaseProductName()}, opening (and
     * closing) one connection.
     *
     * @param dataSource
     *                       The data source to inspect.
     * @throws IllegalStateException
     *                                      If a connection or its metadata
     *                                      cannot be obtained.
     * @throws IllegalArgumentException
     *                                      If the product is not a supported
     *                                      platform.
     */
    public static DatabasePlatform fromDataSource(final DataSource dataSource) {
        try (Connection connection = dataSource.getConnection()) {
            return fromProductName(
                    connection.getMetaData().getDatabaseProductName());
        } catch (final SQLException e) {
            throw new IllegalStateException(
                    FAILED_OBTAINING_DB_FROM_DATA_SOURCE_MSG, e);
        }
    }

    /**
     * Maps a JDBC database product name (case-insensitive:
     * {@code "PostgreSQL"}, {@code "MySQL"}, {@code "MariaDB"}, {@code "H2"})
     * to a platform.
     *
     * @param productName
     *                        The JDBC database product name.
     * @throws IllegalArgumentException
     *                                      If the product is not a supported
     *                                      platform.
     */
    public static DatabasePlatform fromProductName(final String productName) {
        final DatabasePlatform databasePlatform;

        final String name =
                productName == null ? "" : productName.toLowerCase(Locale.ROOT);
        if (name.contains("h2")) {
            databasePlatform = H2;
        } else if (name.contains("mariadb")) {
            databasePlatform = MARIADB;
        } else if (name.contains("mysql")) {
            databasePlatform = MYSQL;
        } else if (name.contains("postgres")) {
            databasePlatform = POSTGRESQL;
        } else {
            throw new IllegalArgumentException(
                    "Unsupported database platform '%s' — supported: PostgreSQL, MySQL, MariaDB, H2"
                            .formatted(productName));
        }

        return databasePlatform;
    }
}