Connection.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.database.base;

import org.flywaydb.core.internal.exception.FlywaySqlException;
import org.flywaydb.core.internal.jdbc.ExecutionTemplateFactory;
import org.flywaydb.core.internal.jdbc.JdbcTemplate;
import org.flywaydb.core.internal.jdbc.JdbcUtils;

import java.io.Closeable;
import java.sql.SQLException;
import java.util.concurrent.Callable;

public abstract class Connection<D extends Database> implements Closeable {
    protected final D database;
    protected JdbcTemplate jdbcTemplate;
    private final java.sql.Connection jdbcConnection;
    protected final String originalSchemaNameOrSearchPath;
    private final boolean originalAutoCommit;

    protected Connection(D database, java.sql.Connection connection) {
        this.database = database;

        try {
            this.originalAutoCommit = connection.getAutoCommit();
            if (!originalAutoCommit) {
                connection.setAutoCommit(true);
            }
        } catch (SQLException e) {
            throw new FlywaySqlException("Unable to turn on auto-commit for the connection", e);
        }

        this.jdbcConnection = connection;
        jdbcTemplate = new JdbcTemplate(jdbcConnection, database.getDatabaseType());
        try {
            originalSchemaNameOrSearchPath = getCurrentSchemaNameOrSearchPath();
        } catch (SQLException e) {
            throw new FlywaySqlException("Unable to determine the original schema for the connection", e);
        }
    }

    /**
     * @throws SQLException when the current schema name or search path could not be retrieved.
     */
    protected abstract String getCurrentSchemaNameOrSearchPath() throws SQLException;

    public final Schema getCurrentSchema() {
        try {
            return doGetCurrentSchema();
        } catch (SQLException e) {
            throw new FlywaySqlException("Unable to determine the current schema for the connection", e);
        }
    }

    protected Schema doGetCurrentSchema() throws SQLException {
        return getSchema(getCurrentSchemaNameOrSearchPath());
    }

    /**
     * Retrieves the schema with this name in the database.
     */
    public abstract Schema getSchema(String name);

    public void changeCurrentSchemaTo(Schema schema) {
        try {
            if (!schema.exists()) {
                return;
            }
            doChangeCurrentSchemaOrSearchPathTo(schema.getName());
        } catch (SQLException e) {
            throw new FlywaySqlException("Error setting current schema to " + schema, e);
        }
    }

    /**
     * @param schemaNameOrSearchPath The new current schema for this connection.
     * @throws SQLException when the current schema could not be set.
     */
    protected void doChangeCurrentSchemaOrSearchPathTo(String schemaNameOrSearchPath) throws SQLException {}

    /**
     * Locks this table and executes this callable.
     *
     * @return The result of the callable.
     */
    public <T> T lock(final Table table, final Callable<T> callable) {
        return ExecutionTemplateFactory
                .createTableExclusiveExecutionTemplate(jdbcTemplate.getConnection(), table, database)
                .execute(callable);
    }

    public final JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    @Override
    public final void close() {
        restoreOriginalState();
        restoreOriginalSchema();
        restoreOriginalAutoCommit();
        JdbcUtils.closeConnection(jdbcConnection);
    }

    private void restoreOriginalSchema() {
        ExecutionTemplateFactory.createExecutionTemplate(jdbcConnection, database).execute((Callable<Void>) () -> {
            try {
                doChangeCurrentSchemaOrSearchPathTo(originalSchemaNameOrSearchPath);
            } catch (SQLException e) {
                throw new FlywaySqlException("Unable to restore original schema", e);
            }
            return null;
        });
    }

    public final void restoreOriginalState() {
        try {
            doRestoreOriginalState();
        } catch (SQLException e) {
            throw new FlywaySqlException("Unable to restore connection to its original state", e);
        }
    }

    private void restoreOriginalAutoCommit() {
        try {
            jdbcConnection.setAutoCommit(originalAutoCommit);
        } catch (SQLException e) {
            throw new FlywaySqlException("Unable to restore connection to its original auto-commit setting", e);
        }
    }

    protected void doRestoreOriginalState() throws SQLException {}

    public final java.sql.Connection getJdbcConnection() {
        return jdbcConnection;
    }
}