FirebirdSchema.java

/*-
 * ========================LICENSE_START=================================
 * flyway-firebird
 * ========================================================================
 * Copyright (C) 2010 - 2026 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.database.firebird;

import org.flywaydb.core.internal.database.base.Schema;
import org.flywaydb.core.internal.database.base.Table;
import org.flywaydb.core.internal.jdbc.JdbcTemplate;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class FirebirdSchema extends Schema<FirebirdDatabase, FirebirdTable> {

    public FirebirdSchema(final JdbcTemplate jdbcTemplate, final FirebirdDatabase database, final String name) {
        super(jdbcTemplate, database, name);
    }

    @Override
    protected boolean doExists() {
        // database == schema, always return true
        return true;
    }

    @Override
    protected boolean doEmpty() throws SQLException {
        // database == schema, check content of database
        // Check for all object types except custom collations and roles
        return 0 == jdbcTemplate.queryForInt("select count(*)\n"
            + "from (\n"
            + "  -- views and tables\n"
            + "  select RDB$RELATION_NAME AS OBJECT_NAME\n"
            + "  from RDB$RELATIONS\n"
            + "  where (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)\n"
            + "  union all\n"
            + "  -- stored procedures\n"
            + "  select RDB$PROCEDURE_NAME\n"
            + "  from RDB$PROCEDURES\n"
            + "  where (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)\n"
            + "  union all\n"
            + "  -- triggers\n"
            + "  select RDB$TRIGGER_NAME\n"
            + "  from RDB$TRIGGERS\n"
            + "  where (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)\n"
            + "  union all\n"
            + "  -- functions\n"
            + "  select RDB$FUNCTION_NAME\n"
            + "  from RDB$FUNCTIONS\n"
            + "  where (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)\n"
            + "  union all\n"
            + "  -- sequences\n"
            + "  select RDB$GENERATOR_NAME\n"
            + "  from RDB$GENERATORS\n"
            + "  where (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)\n"
            + "  union all\n"
            + "  -- exceptions\n"
            + "  select RDB$EXCEPTION_NAME\n"
            + "  from RDB$EXCEPTIONS\n"
            + "  where (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)\n"
            + "  union all\n"
            + "  -- domains\n"
            + "  select RDB$FIELD_NAME\n"
            + "  from RDB$FIELDS\n"
            + "  where RDB$FIELD_NAME not starting with 'RDB$'\n"
            + "  and (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)\n"
            + "union all\n"
            + "-- packages\n"
            + "select RDB$PACKAGE_NAME\n"
            + "from RDB$PACKAGES\n"
            + "where (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)) a");
    }

    @Override
    protected void doCreate() {
        // database == schema, do nothing for creation
    }

    @Override
    protected void doDrop() throws SQLException {
        // database == schema, doClean() instead
        doClean();
    }

    @Override
    protected void doClean() throws SQLException {
        // Dropping everything except custom collations and roles
        for (final String dropPackageStmt : generateDropPackageStatements()) {
            jdbcTemplate.execute(dropPackageStmt);
        }
        for (final String dropProcedureStmt : generateDropProcedureStatements()) {
            jdbcTemplate.execute(dropProcedureStmt);
        }
        for (final String dropViewStmt : generateDropViewStatements()) {
            jdbcTemplate.execute(dropViewStmt);
        }

        for (final String dropConstraintStmt : generateDropConstraintStatements()) {
            jdbcTemplate.execute(dropConstraintStmt);
        }

        for (final Table table : allTables()) {
            table.drop();
        }
        for (final String dropTriggerStmt : generateDropTriggerStatements()) {
            jdbcTemplate.execute(dropTriggerStmt);
        }
        for (final String dropFunctionStmt : generateDropFunctionStatements()) {
            jdbcTemplate.execute(dropFunctionStmt);
        }
        for (final String dropSequenceStmt : generateDropSequenceStatements()) {
            jdbcTemplate.execute(dropSequenceStmt);
        }
        for (final String dropExceptionStmt : generateDropExceptionStatements()) {
            jdbcTemplate.execute(dropExceptionStmt);
        }
        for (final String dropDomainStmt : generateDropDomainStatements()) {
            jdbcTemplate.execute(dropDomainStmt);
        }
    }

    private List<String> generateDropConstraintStatements() throws SQLException {
        return jdbcTemplate.query("select RDB$RELATION_NAME, RDB$CONSTRAINT_NAME\n"
            + "from RDB$RELATION_CONSTRAINTS\n"
            + "where RDB$RELATION_NAME NOT LIKE 'RDB$%'\n"
            + "and RDB$CONSTRAINT_TYPE='FOREIGN KEY'", rs -> {
            final String tableName = rs.getString(1);
            final String constraintName = rs.getString(2);
            return "ALTER TABLE " + tableName + " DROP CONSTRAINT " + constraintName;
        });
    }

    private List<String> generateDropPackageStatements() throws SQLException {
        final List<String> packageNames = jdbcTemplate.queryForStringList("select RDB$PACKAGE_NAME as packageName\n"
            + "from RDB$PACKAGES\n"
            + "where (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)");

        return generateDropStatements("package", packageNames);
    }

    private List<String> generateDropProcedureStatements() throws SQLException {
        final List<String> procedureNames = jdbcTemplate.queryForStringList(
            "select RDB$PROCEDURE_NAME as procedureName\n"
                + "from RDB$PROCEDURES\n"
                + "where (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)"
                + "\nand RDB$PACKAGE_NAME is null");

        return generateDropStatements("procedure", procedureNames);
    }

    private List<String> generateDropViewStatements() throws SQLException {
        final List<String> viewNames = jdbcTemplate.queryForStringList("select RDB$RELATION_NAME as viewName\n"
            + "from RDB$RELATIONS\n"
            + "where RDB$VIEW_BLR is not null\n"
            + "and (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)");

        return generateDropStatements("view", viewNames);
    }

    private List<String> generateDropTriggerStatements() throws SQLException {
        final List<String> triggerNames = jdbcTemplate.queryForStringList("select RDB$TRIGGER_NAME as triggerName\n"
            + "from RDB$TRIGGERS\n"
            + "where (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)\n");

        return generateDropStatements("trigger", triggerNames);
    }

    private List<String> generateDropFunctionStatements() throws SQLException {
        final List<String> functionNames = jdbcTemplate.queryForStringList("select RDB$FUNCTION_NAME as functionName\n"
            + "from RDB$FUNCTIONS\n"
            + "where (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)");

        final String functionTypeName = database.getVersion().isAtLeast("3.0") ? "function" : "external function";
        return generateDropStatements(functionTypeName, functionNames);
    }

    private List<String> generateDropSequenceStatements() throws SQLException {
        final List<String> sequenceNames = jdbcTemplate.queryForStringList("select RDB$GENERATOR_NAME as sequenceName\n"
            + "from RDB$GENERATORS\n"
            + "where (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)\n");

        return generateDropStatements("sequence", sequenceNames);
    }

    private List<String> generateDropExceptionStatements() throws SQLException {
        final List<String> exceptionNames = jdbcTemplate.queryForStringList(
            "select RDB$EXCEPTION_NAME as exceptionName\n"
                + "from RDB$EXCEPTIONS\n"
                + "where (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)\n");

        return generateDropStatements("exception", exceptionNames);
    }

    private List<String> generateDropDomainStatements() throws SQLException {
        final List<String> domainNames = jdbcTemplate.queryForStringList("select RDB$FIELD_NAME as domainName\n"
            + "from RDB$FIELDS\n"
            + "where RDB$FIELD_NAME not starting with 'RDB$'\n"
            + "and (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)\n");

        return generateDropStatements("domain", domainNames);
    }

    private List<String> generateDropStatements(final String objectType, final List<String> objectNames) {
        final List<String> statements = new ArrayList<>(objectNames.size());
        for (final String objectName : objectNames) {
            statements.add("drop " + objectType + " " + database.quote(objectName));
        }
        return statements;
    }

    @Override
    protected FirebirdTable[] doAllTables() throws SQLException {
        final List<String> tableNames = jdbcTemplate.queryForStringList("select RDB$RELATION_NAME as tableName\n"
            + "from RDB$RELATIONS\n"
            + "where RDB$VIEW_BLR is null\n"
            + "and (RDB$SYSTEM_FLAG is null or RDB$SYSTEM_FLAG = 0)");

        final FirebirdTable[] tables = new FirebirdTable[tableNames.size()];
        for (int i = 0; i < tableNames.size(); i++) {
            tables[i] = getTable(tableNames.get(i));
        }

        return tables;
    }

    @Override
    public FirebirdTable getTable(final String tableName) {
        return new FirebirdTable(jdbcTemplate, database, this, tableName);
    }
}