<commit_before>/*
 * Copyright (C) Red Gate Software Ltd 2010-2022
 *
 * 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.
 */
package org.flywaydb.core.internal.jdbc;

import lombok.CustomLog;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.internal.database.DatabaseTypeRegister;
import org.flywaydb.core.internal.exception.FlywaySqlException;
import org.flywaydb.core.internal.strategy.BackoffStrategy;
import org.flywaydb.core.internal.util.ExceptionUtils;
import org.flywaydb.core.internal.util.FlywayDbWebsiteLinks;

import javax.sql.DataSource;
import java.sql.*;

/**
 * Utility class for dealing with jdbc connections.
 */
@CustomLog
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class JdbcUtils {

    /**
     * Opens a new connection from this dataSource.
     *
     * @param dataSource The dataSource to obtain the connection from.
     * @param connectRetries The maximum number of retries when attempting to connect to the database.
     * @param connectRetriesInterval The maximum time between retries in seconds
     * @return The new connection.
     * @throws FlywayException when the connection could not be opened.
     */
    public static Connection openConnection(DataSource dataSource, int connectRetries, int connectRetriesInterval) throws FlywayException {
        BackoffStrategy backoffStrategy = new BackoffStrategy(1, 2, connectRetriesInterval);
        int retries = 0;
        while (true) {
            try {
                return dataSource.getConnection();
            } catch (SQLException e) {
                if ("08S01".equals(e.getSQLState()) && e.getMessage().contains("This driver is not configured for integrated authentication")) {
                    throw new FlywaySqlException("Unable to obtain connection from database"
                                                         + getDataSourceInfo(dataSource) + ": " + e.getMessage() + "\nTo setup integrated authentication see " +
                                                         FlywayDbWebsiteLinks.WINDOWS_AUTH, e);
                } else if (e.getSQLState() == null && e.getMessage().contains("MSAL4J")) {
                    throw new FlywaySqlException("Unable to obtain connection from database"
                                                         + getDataSourceInfo(dataSource) + ": " + e.getMessage() +
                                                         "\nYou need to install some extra drivers in order for interactive authentication to work." +
                                                         "\nFor instructions, see " + FlywayDbWebsiteLinks.AZURE_ACTIVE_DIRECTORY, e);
                }

                if (++retries > connectRetries) {
                    throw new FlywaySqlException("Unable to obtain connection from database"
                                                         + getDataSourceInfo(dataSource) + ": " + e.getMessage(), e);
                }
                Throwable rootCause = ExceptionUtils.getRootCause(e);
                String msg = "Connection error: " + e.getMessage();
                if (rootCause != null && rootCause != e && rootCause.getMessage() != null) {
                    msg += " (Caused by " + rootCause.getMessage() + ")";
                }
                LOG.warn(msg + " Retrying in " + backoffStrategy.peek() + " sec...");
                try {
                    Thread.sleep(backoffStrategy.next() * 1000);
                } catch (InterruptedException e1) {
                    throw new FlywaySqlException("Unable to obtain connection from database"
                                                         + getDataSourceInfo(dataSource) + ": " + e.getMessage(), e);
                }
            }
        }
    }

    private static String getDataSourceInfo(DataSource dataSource) {
        if (!(dataSource instanceof DriverDataSource)) {
            return "";
        }
        DriverDataSource driverDataSource = (DriverDataSource) dataSource;
        return " (" + DatabaseTypeRegister.redactJdbcUrl(driverDataSource.getUrl()) + ") for user '" + driverDataSource.getUser() + "'";
    }

    /**
     * Safely closes this connection. This method never fails.
     */
    public static void closeConnection(Connection connection) {
        if (connection == null) {
            return;
        }

        try {
            connection.close();
        } catch (Exception e) {
            LOG.error("Error while closing database connection: " + e.getMessage(), e);
        }
    }

    /**
     * Safely closes this statement. This method never fails.
     */
    public static void closeStatement(Statement statement) {
        if (statement == null) {
            return;
        }

        try {
            statement.close();
        } catch (SQLException e) {
            LOG.error("Error while closing JDBC statement", e);
        }
    }

    /**
     * Safely closes this resultSet. This method never fails.
     */
    public static void closeResultSet(ResultSet resultSet) {
        if (resultSet == null) {
            return;
        }

        try {
            resultSet.close();
        } catch (SQLException e) {
            LOG.error("Error while closing JDBC resultSet", e);
        }
    }

    public static DatabaseMetaData getDatabaseMetaData(Connection connection) {
        DatabaseMetaData databaseMetaData;
        try {
            databaseMetaData = connection.getMetaData();
        } catch (SQLException e) {
            throw new FlywaySqlException("Unable to read database connection metadata: " + e.getMessage(), e);
        }
        if (databaseMetaData == null) {
            throw new FlywayException("Unable to read database connection metadata while it is null!");
        }
        return databaseMetaData;
    }

    /**
     * @return The name of the database product. Example: Oracle, MySQL, ...
     */
    public static String getDatabaseProductName(DatabaseMetaData databaseMetaData) {
        try {
            String databaseProductName = databaseMetaData.getDatabaseProductName();
            if (databaseProductName == null) {
                throw new FlywayException("Unable to determine database. Product name is null.");
            }

            int databaseMajorVersion = databaseMetaData.getDatabaseMajorVersion();
            int databaseMinorVersion = databaseMetaData.getDatabaseMinorVersion();

            return databaseProductName + " " + databaseMajorVersion + "." + databaseMinorVersion;
        } catch (SQLException e) {
            throw new FlywaySqlException("Error while determining database product name", e);
        }
    }

    /**
     * @return The version of the database product. Example: MariaDB 10.3, ...
     */
    public static String getDatabaseProductVersion(DatabaseMetaData databaseMetaData) {
        try {
            return databaseMetaData.getDatabaseProductVersion();
        } catch (SQLException e) {
            throw new FlywaySqlException("Error while determining database product version", e);<commit_msg>Suppress `user 'null'` when using Windows Auth<commit_after>/*
 * Copyright (C) Red Gate Software Ltd 2010-2022
 *
 * 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.
 */
package org.flywaydb.core.internal.jdbc;

import lombok.CustomLog;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.internal.database.DatabaseTypeRegister;
import org.flywaydb.core.internal.exception.FlywaySqlException;
import org.flywaydb.core.internal.strategy.BackoffStrategy;
import org.flywaydb.core.internal.util.ExceptionUtils;
import org.flywaydb.core.internal.util.FlywayDbWebsiteLinks;

import javax.sql.DataSource;
import java.sql.*;

@CustomLog
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class JdbcUtils {
    /**
     * Opens a new connection from this DataSource.
     *
     * @param dataSource The DataSource to obtain the connection from.
     * @param connectRetries The maximum number of retries when attempting to connect to the database.
     * @param connectRetriesInterval The maximum time between retries in seconds.
     * @return The new connection.
     * @throws FlywayException when the connection could not be opened.
     */
    public static Connection openConnection(DataSource dataSource, int connectRetries, int connectRetriesInterval) throws FlywayException {
        BackoffStrategy backoffStrategy = new BackoffStrategy(1, 2, connectRetriesInterval);
        int retries = 0;
        while (true) {
            try {
                return dataSource.getConnection();
            } catch (SQLException e) {
                if ("08S01".equals(e.getSQLState()) && e.getMessage().contains("This driver is not configured for integrated authentication")) {
                    throw new FlywaySqlException("Unable to obtain connection from database" + getDataSourceInfo(dataSource, true) + ": " + e.getMessage() +
                                                         "\nTo setup integrated authentication see " + FlywayDbWebsiteLinks.WINDOWS_AUTH, e);
                } else if (e.getSQLState() == null && e.getMessage().contains("MSAL4J")) {
                    throw new FlywaySqlException("Unable to obtain connection from database" + getDataSourceInfo(dataSource, false) + ": " + e.getMessage() +
                                                         "\nYou need to install some extra drivers in order for interactive authentication to work." +
                                                         "\nFor instructions, see " + FlywayDbWebsiteLinks.AZURE_ACTIVE_DIRECTORY, e);
                }

                if (++retries > connectRetries) {
                    throw new FlywaySqlException("Unable to obtain connection from database" + getDataSourceInfo(dataSource, false) + ": " + e.getMessage(), e);
                }
                Throwable rootCause = ExceptionUtils.getRootCause(e);
                String message = "Connection error: " + e.getMessage();
                if (rootCause != null && rootCause != e && rootCause.getMessage() != null) {
                    message += "\n(Caused by " + rootCause.getMessage() + ")";
                }
                LOG.warn(message + "\nRetrying in " + backoffStrategy.peek() + " sec...");
                try {
                    Thread.sleep(backoffStrategy.next() * 1000L);
                } catch (InterruptedException e1) {
                    throw new FlywaySqlException("Unable to obtain connection from database" + getDataSourceInfo(dataSource, false) + ": " + e.getMessage(), e);
                }
            }
        }
    }

    private static String getDataSourceInfo(DataSource dataSource, boolean suppressNullUserMessage) {
        if (!(dataSource instanceof DriverDataSource)) {
            return "";
        }
        DriverDataSource driverDataSource = (DriverDataSource) dataSource;
        String user = driverDataSource.getUser();
        String info = " (" + DatabaseTypeRegister.redactJdbcUrl(driverDataSource.getUrl()) + ")";
        if (user != null || !suppressNullUserMessage) {
            info += " for user '" + user + "'";
        }
        return info;
    }

    /**
     * Safely closes this Connection. This method never fails.
     */
    public static void closeConnection(Connection connection) {
        if (connection == null) {
            return;
        }
        try {
            connection.close();
        } catch (Exception e) {
            LOG.error("Error while closing database Connection: " + e.getMessage(), e);
        }
    }

    /**
     * Safely closes this Statement. This method never fails.
     */
    public static void closeStatement(Statement statement) {
        if (statement == null) {
            return;
        }
        try {
            statement.close();
        } catch (SQLException e) {
            LOG.error("Error while closing JDBC Statement", e);
        }
    }

    /**
     * Safely closes this ResultSet. This method never fails.
     */
    public static void closeResultSet(ResultSet resultSet) {
        if (resultSet == null) {
            return;
        }
        try {
            resultSet.close();
        } catch (SQLException e) {
            LOG.error("Error while closing JDBC ResultSet", e);
        }
    }

    public static DatabaseMetaData getDatabaseMetaData(Connection connection) {
        DatabaseMetaData databaseMetaData;
        try {
            databaseMetaData = connection.getMetaData();
        } catch (SQLException e) {
            throw new FlywaySqlException("Unable to read database connection metadata: " + e.getMessage(), e);
        }
        if (databaseMetaData == null) {
            throw new FlywayException("Unable to read database connection metadata while it is null!");
        }
        return databaseMetaData;
    }

    /**
     * @return The name of the database product. Example: Oracle, MySQL, ...
     */
    public static String getDatabaseProductName(DatabaseMetaData databaseMetaData) {
        try {
            String databaseProductName = databaseMetaData.getDatabaseProductName();
            if (databaseProductName == null) {
                throw new FlywayException("Unable to determine database. Product name is null.");
            }

            return databaseProductName + " " + databaseMetaData.getDatabaseMajorVersion() + "." + databaseMetaData.getDatabaseMinorVersion();
        } catch (SQLException e) {
            throw new FlywaySqlException("Error while determining database product name", e);
        }
    }

    /**
     * @return The version of the database product. Example: MariaDB 10.3, ...
     */
    public static String getDatabaseProductVersion(DatabaseMetaData databaseMetaData) {
        try {
            return databaseMetaData.getDatabaseProductVersion();
        } catch (SQLException e) {
            throw new FlywaySqlException("Error while determining database product version", e);
{'fraction_non_alphanumeric': 0.0899390243902439, 'fraction_numerical': 0.003711558854718982, 'mean_word_length': 2.228974962550824, 'pattern_counts': {'":': 8, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3022106', 'n_tokens_mistral': 3625, 'n_tokens_neox': 3537, 'n_words': 1103}