All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.mengweijin.flyway.database.dm.DmSchema Maven / Gradle / Ivy

Go to download

Flyway、Liquibase 扩展支持达梦(DM)数据库、南大通用(GBase 8s)数据库。

There is a newer version: 2.0.2
Show newest version
package com.github.mengweijin.flyway.database.dm;

import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.api.logging.Log;
import org.flywaydb.core.api.logging.LogFactory;
import org.flywaydb.core.internal.database.base.Schema;
import org.flywaydb.core.internal.database.base.Table;
import org.flywaydb.core.internal.jdbc.JdbcTemplate;
import org.flywaydb.core.internal.util.StringUtils;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * DM implementation of Schema.
 */
public class DmSchema extends Schema {

    private static final Log LOG = LogFactory.getLog(DmSchema.class);

    /**
     * Creates a new DM schema.
     *
     * @param jdbcTemplate The Jdbc Template for communicating with the DB.
     * @param database     The database-specific support.
     * @param name         The name of the schema.
     */
    DmSchema(JdbcTemplate jdbcTemplate, DmDatabase database, String name) {
        super(jdbcTemplate, database, name);
    }

    /**
     * Checks whether the schema is system, i.e. DM-maintained, or not.
     *
     * @return {@code true} if it is system, {@code false} if not.
     */
    public boolean isSystem() throws SQLException {
        return database.getSystemSchemas().contains(name);
    }

    /**
     * Checks whether this schema is default for the current user.
     *
     * @return {@code true} if it is default, {@code false} if not.
     */
    boolean isDefaultSchemaForUser() throws SQLException {
        return name.equals(database.doGetCurrentUser());
    }

    /**
     * return database.queryReturnsRows("SELECT * FROM ALL_USERS WHERE USERNAME = ?", name);
     * https://github.com/mengweijin/db-migration/issues/7
     * return database.queryReturnsRows("SELECT DISTINCT OBJECT_NAME FROM ALL_OBJECTS WHERE OBJECT_TYPE = 'SCH' AND OBJECT_NAME = ?", name);
     *
     * @return boolean
     * @throws SQLException SQLException
     */
    @Override
    protected boolean doExists() throws SQLException {
        // return database.queryReturnsRows("SELECT * FROM ALL_USERS WHERE USERNAME = ?", name);
        return database.queryReturnsRows("SELECT DISTINCT OBJECT_NAME FROM ALL_OBJECTS WHERE OBJECT_TYPE = 'SCH' AND OBJECT_NAME = ?", name);
    }

    @Override
    protected boolean doEmpty() throws SQLException {
        return !ObjectType.supportedTypesExist(jdbcTemplate, database, this);
    }

    @Override
    protected void doCreate() throws SQLException {
        jdbcTemplate.execute("CREATE USER " + database.quote(name) + " IDENTIFIED BY "
                + database.quote("FFllyywwaayy00!!"));
        jdbcTemplate.execute("GRANT RESOURCE TO " + database.quote(name));
        jdbcTemplate.execute("GRANT UNLIMITED TABLESPACE TO " + database.quote(name));
    }

    @Override
    protected void doDrop() throws SQLException {
        jdbcTemplate.execute("DROP USER " + database.quote(name) + " CASCADE");
    }

    @Override
    protected void doClean() throws SQLException {
        if (isSystem()) {
            throw new FlywayException("Clean not supported on DM for system schema " + database.quote(name) + "! " +
                    "It must not be changed in any way except by running an DM-supplied script!");
        }

        // Disable FBA for schema tables.
        if (database.isFlashbackDataArchiveAvailable()) {
            disableFlashbackArchiveForFbaTrackedTables();
        }

        // Clean DM Locator metadata.
        if (database.isLocatorAvailable()) {
            cleanLocatorMetadata();
        }

        // Get existing object types in the schema.
        Set objectTypeNames = ObjectType.getObjectTypeNames(jdbcTemplate, database, this);

        // Define the list of types to process, order is important.
        List objectTypesToClean = Arrays.asList(
                // Types to drop.
                ObjectType.TRIGGER,
                ObjectType.QUEUE_TABLE,
                ObjectType.FILE_WATCHER,
                ObjectType.SCHEDULER_CHAIN,
                ObjectType.SCHEDULER_JOB,
                ObjectType.SCHEDULER_PROGRAM,
                ObjectType.SCHEDULE,
                ObjectType.RULE_SET,
                ObjectType.RULE,
                ObjectType.EVALUATION_CONTEXT,
                ObjectType.FILE_GROUP,
                ObjectType.XML_SCHEMA,
                ObjectType.MINING_MODEL,
                ObjectType.REWRITE_EQUIVALENCE,
                ObjectType.SQL_TRANSLATION_PROFILE,
                ObjectType.MATERIALIZED_VIEW,
                ObjectType.MATERIALIZED_VIEW_LOG,
                ObjectType.DIMENSION,
                ObjectType.VIEW,
                ObjectType.DOMAIN_INDEX,
                ObjectType.DOMAIN_INDEX_TYPE,
                ObjectType.TABLE,
                ObjectType.INDEX,
                ObjectType.CLUSTER,
                ObjectType.SEQUENCE,
                ObjectType.OPERATOR,
                ObjectType.FUNCTION,
                ObjectType.PROCEDURE,
                ObjectType.PACKAGE,
                ObjectType.PACKAGE_BODY,
                ObjectType.CONTEXT,
                ObjectType.LIBRARY,
                ObjectType.TYPE,
                ObjectType.SYNONYM,
                ObjectType.JAVA_SOURCE,
                ObjectType.JAVA_CLASS,
                ObjectType.JAVA_RESOURCE,

                // Object types with sensitive information (passwords), skip intentionally, print warning if found.
                ObjectType.DATABASE_LINK,
                ObjectType.CREDENTIAL,

                // Unsupported types, print warning if found
                ObjectType.DATABASE_DESTINATION,
                ObjectType.SCHEDULER_GROUP,
                ObjectType.CUBE,
                ObjectType.CUBE_DIMENSION,
                ObjectType.CUBE_BUILD_PROCESS,
                ObjectType.MEASURE_FOLDER,

                // Undocumented types, print warning if found
                ObjectType.ASSEMBLY,
                ObjectType.JAVA_DATA
        );

        for (ObjectType objectType : objectTypesToClean) {
            if (objectTypeNames.contains(objectType.getName())) {
                LOG.debug("Cleaning objects of type " + objectType + " ...");
                objectType.dropObjects(jdbcTemplate, database, this);
            }
        }

        if (isDefaultSchemaForUser()) {
            jdbcTemplate.execute("PURGE RECYCLEBIN");
        }
    }

    /**
     * Executes ALTER statements for all tables that have Flashback Archive enabled.
     * Flashback Archive is an asynchronous process so we need to wait until it completes, otherwise cleaning the
     * tables in schema will sometimes fail with ORA-55622 or ORA-55610 depending on the race between
     * Flashback Archive and Java code.
     *
     * @throws SQLException when the statements could not be generated.
     */
    private void disableFlashbackArchiveForFbaTrackedTables() throws SQLException {
        boolean dbaViewAccessible = database.isPrivOrRoleGranted("SELECT ANY DICTIONARY")
                || database.isDataDictViewAccessible("DBA_FLASHBACK_ARCHIVE_TABLES");

        if (!dbaViewAccessible && !isDefaultSchemaForUser()) {
            LOG.warn("Unable to check and disable Flashback Archive for tables in schema " + database.quote(name) +
                    " by user \"" + database.doGetCurrentUser() + "\": DBA_FLASHBACK_ARCHIVE_TABLES is not accessible");
            return;
        }

        boolean oracle18orNewer = database.getVersion().isAtLeast("18");

        String queryForFbaTrackedTables = "SELECT TABLE_NAME FROM " + (dbaViewAccessible ? "DBA_" : "USER_")
                + "FLASHBACK_ARCHIVE_TABLES WHERE OWNER_NAME = ?"
                + (oracle18orNewer ? " AND STATUS='ENABLED'" : "");
        List tableNames = jdbcTemplate.queryForStringList(queryForFbaTrackedTables, name);
        for (String tableName : tableNames) {
            jdbcTemplate.execute("ALTER TABLE " + database.quote(name, tableName) + " NO FLASHBACK ARCHIVE");
            //wait until the tables disappear
            while (database.queryReturnsRows(queryForFbaTrackedTables + " AND TABLE_NAME = ?", name, tableName)) {
                try {
                    LOG.debug("Actively waiting for Flashback cleanup on table: " + database.quote(name, tableName));
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new FlywayException("Waiting for Flashback cleanup interrupted", e);
                }
            }
        }

        if (oracle18orNewer) {
            while (database.queryReturnsRows("SELECT TABLE_NAME FROM ALL_TABLES WHERE OWNER = ?\n"
                    + " AND TABLE_NAME LIKE 'SYS_FBA_DDL_COLMAP_%'", name)) {
                try {
                    LOG.debug("Actively waiting for Flashback colmap cleanup");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new FlywayException("Waiting for Flashback colmap cleanup interrupted", e);
                }
            }
        }
    }

    /**
     * Checks whether DM Locator metadata exists for the schema.
     *
     * @return {@code true} if it exists, {@code false} if not.
     * @throws SQLException when checking metadata existence failed.
     */
    private boolean locatorMetadataExists() throws SQLException {
        return database.queryReturnsRows("SELECT * FROM ALL_SDO_GEOM_METADATA WHERE OWNER = ?", name);
    }

    /**
     * Clean DM Locator metadata for the schema. Works only for the user's default schema, prints a warning message
     * to log otherwise.
     *
     * @throws SQLException when performing cleaning failed.
     */
    private void cleanLocatorMetadata() throws SQLException {
        if (!locatorMetadataExists()) {
            return;
        }

        if (!isDefaultSchemaForUser()) {
            LOG.warn("Unable to clean DM Locator metadata for schema " + database.quote(name) +
                    " by user \"" + database.doGetCurrentUser() + "\": unsupported operation");
            return;
        }

        jdbcTemplate.getConnection().commit();
        jdbcTemplate.execute("DELETE FROM USER_SDO_GEOM_METADATA");
        jdbcTemplate.getConnection().commit();
    }

    @Override
    protected DmTable[] doAllTables() throws SQLException {
        List tableNames = ObjectType.TABLE.getObjectNames(jdbcTemplate, database, this);

        DmTable[] tables = new DmTable[tableNames.size()];
        for (int i = 0; i < tableNames.size(); i++) {
            tables[i] = new DmTable(jdbcTemplate, database, this, tableNames.get(i));
        }
        return tables;
    }

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

    /**
     * DM object types.
     */
    public enum ObjectType {
        // Tables, including XML tables, except for nested tables, IOT overflow tables and other secondary objects.
        TABLE("TABLE", "CASCADE CONSTRAINTS PURGE") {
            @Override
            public List getObjectNames(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) throws SQLException {
                boolean referencePartitionedTablesExist = database.queryReturnsRows(
                        "SELECT * FROM ALL_PART_TABLES WHERE OWNER = ? AND PARTITIONING_TYPE = 'REFERENCE'",
                        schema.getName());
                boolean xmlDbAvailable = database.isXmlDbAvailable();

                StringBuilder tablesQuery = new StringBuilder();
                tablesQuery.append("WITH TABLES AS (\n" +
                        "  SELECT TABLE_NAME, OWNER\n" +
                        "  FROM ALL_TABLES\n" +
                        "  WHERE OWNER = ?\n" +
                        "    AND (IOT_TYPE IS NULL OR IOT_TYPE NOT LIKE '%OVERFLOW%')\n" +
                        "    AND NESTED != 'YES'\n" +
                        "    AND SECONDARY != 'Y'\n");

                if (xmlDbAvailable) {
                    tablesQuery.append("  UNION ALL\n" +
                            "  SELECT TABLE_NAME, OWNER\n" +
                            "  FROM ALL_XML_TABLES\n" +
                            "  WHERE OWNER = ?\n" +
                            // ALL_XML_TABLES shows objects in RECYCLEBIN, ignore them
                            "    AND TABLE_NAME NOT LIKE 'BIN$________________________$_'\n");
                }

                tablesQuery.append(")\n" +
                        "SELECT t.TABLE_NAME\n" +
                        "FROM TABLES t\n");

                // Reference partitioned tables should be dropped in child-to-parent order.
                if (referencePartitionedTablesExist) {
                    tablesQuery.append("  LEFT JOIN ALL_PART_TABLES pt\n" +
                            "    ON t.OWNER = pt.OWNER\n" +
                            "   AND t.TABLE_NAME = pt.TABLE_NAME\n" +
                            "   AND pt.PARTITIONING_TYPE = 'REFERENCE'\n" +
                            "  LEFT JOIN ALL_CONSTRAINTS fk\n" +
                            "    ON pt.OWNER = fk.OWNER\n" +
                            "   AND pt.TABLE_NAME = fk.TABLE_NAME\n" +
                            "   AND pt.REF_PTN_CONSTRAINT_NAME = fk.CONSTRAINT_NAME\n" +
                            "   AND fk.CONSTRAINT_TYPE = 'R'\n" +
                            "  LEFT JOIN ALL_CONSTRAINTS puk\n" +
                            "    ON fk.R_OWNER = puk.OWNER\n" +
                            "   AND fk.R_CONSTRAINT_NAME = puk.CONSTRAINT_NAME\n" +
                            "   AND puk.CONSTRAINT_TYPE IN ('P', 'U')\n" +
                            "  LEFT JOIN TABLES p\n" +
                            "    ON puk.OWNER = p.OWNER\n" +
                            "   AND puk.TABLE_NAME = p.TABLE_NAME\n" +
                            "START WITH p.TABLE_NAME IS NULL\n" +
                            "CONNECT BY PRIOR t.TABLE_NAME = p.TABLE_NAME\n" +
                            "ORDER BY LEVEL DESC");
                }

                int n = 1 + (xmlDbAvailable ? 1 : 0);
                String[] params = new String[n];
                Arrays.fill(params, schema.getName());

                return jdbcTemplate.queryForStringList(tablesQuery.toString(), params);
            }
        },

        // Queue tables, have related objects and should be dropped separately prior to other types.
        QUEUE_TABLE("QUEUE TABLE") {
            @Override
            public List getObjectNames(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) throws SQLException {
                return jdbcTemplate.queryForStringList(
                        "SELECT QUEUE_TABLE FROM ALL_QUEUE_TABLES WHERE OWNER = ?",
                        schema.getName()
                );
            }

            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN DBMS_AQADM.DROP_QUEUE_TABLE('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
            }
        },

        // Materialized view logs.
        MATERIALIZED_VIEW_LOG("MATERIALIZED VIEW LOG") {
            @Override
            public List getObjectNames(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) throws SQLException {
                return jdbcTemplate.queryForStringList(
                        "SELECT MASTER FROM ALL_MVIEW_LOGS WHERE LOG_OWNER = ?",
                        schema.getName()
                );
            }

            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "DROP " + this.getName() + " ON " + database.quote(schema.getName(), objectName);
            }
        },

        // All indexes, except for domain indexes, should be dropped after tables (if any left).
        INDEX("INDEX") {
            @Override
            public List getObjectNames(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) throws SQLException {
                return jdbcTemplate.queryForStringList(
                        "SELECT INDEX_NAME FROM ALL_INDEXES WHERE OWNER = ?" +
                                //" AND INDEX_NAME NOT LIKE 'SYS_C%'"+
                                " AND INDEX_TYPE NOT LIKE '%DOMAIN%'",
                        schema.getName()
                );
            }
        },

        // Domain indexes, have related objects and should be dropped separately prior to tables.
        DOMAIN_INDEX("INDEX", "FORCE") {
            @Override
            public List getObjectNames(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) throws SQLException {
                return jdbcTemplate.queryForStringList(
                        "SELECT INDEX_NAME FROM ALL_INDEXES WHERE OWNER = ? AND INDEX_TYPE LIKE '%DOMAIN%'",
                        schema.getName()
                );
            }
        },

        // Domain index types.
        DOMAIN_INDEX_TYPE("INDEXTYPE", "FORCE"),

        // Operators.
        OPERATOR("OPERATOR", "FORCE"),

        // Clusters.
        CLUSTER("CLUSTER", "INCLUDING TABLES CASCADE CONSTRAINTS"),

        // Views, including XML views.
        VIEW("VIEW", "CASCADE CONSTRAINTS"),

        // Materialized views, keep tables as they may be referenced.
        MATERIALIZED_VIEW("MATERIALIZED VIEW", "PRESERVE TABLE"),

        // Dimensions.
        DIMENSION("DIMENSION") {
            @Override
            public List getObjectNames(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) throws SQLException {
                return jdbcTemplate.queryForStringList(
                        "SELECT DIMENSION_NAME FROM ALL_DIMENSIONS WHERE OWNER = ?",
                        schema.getName()
                );
            }
        },

        // Local synonyms.
        SYNONYM("SYNONYM", "FORCE"),

        // Sequences, no filtering for identity sequences, since they get dropped along with master tables.
        SEQUENCE("SEQUENCE"),

        // Procedures, functions, packages.
        PROCEDURE("PROCEDURE"),
        FUNCTION("FUNCTION"),
        PACKAGE("PACKAGE"),
        PACKAGE_BODY("PACKAGE BODY"),

        // Contexts, seen in DBA_CONTEXT view, may remain if DBA_CONTEXT is not accessible.
        CONTEXT("CONTEXT") {
            @Override
            public List getObjectNames(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) throws SQLException {
                return jdbcTemplate.queryForStringList(
                        "SELECT NAMESPACE FROM " + database.dbaOrAll("CONTEXT") + " WHERE SCHEMA = ?",
                        schema.getName()
                );
            }

            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "DROP " + this.getName() + " " + database.quote(objectName); // no owner
            }
        },

        // Triggers of all types, should be dropped at first, because invalid DDL triggers may break the whole clean.
        TRIGGER("TRIGGER"),

        // Types.
        TYPE("TYPE", "FORCE"),

        // Java sources, classes, resources.
        JAVA_SOURCE("JAVA SOURCE"),
        JAVA_CLASS("JAVA CLASS"),
        JAVA_RESOURCE("JAVA RESOURCE"),

        // Libraries.
        LIBRARY("LIBRARY"),

        // XML schemas.
        XML_SCHEMA("XML SCHEMA") {
            @Override
            public List getObjectNames(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) throws SQLException {
                if (!database.isXmlDbAvailable()) {
                    return Collections.emptyList();
                }
                return jdbcTemplate.queryForStringList(
                        "SELECT QUAL_SCHEMA_URL FROM " + database.dbaOrAll("XML_SCHEMAS") + " WHERE OWNER = ?",
                        schema.getName()
                );
            }

            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN DBMS_XMLSCHEMA.DELETESCHEMA('" + objectName + "', DELETE_OPTION => DBMS_XMLSCHEMA.DELETE_CASCADE_FORCE); END;";
            }
        },

        // Rewrite equivalences.
        REWRITE_EQUIVALENCE("REWRITE EQUIVALENCE") {
            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN SYS.DBMS_ADVANCED_REWRITE.DROP_REWRITE_EQUIVALENCE('" + database.quote(schema.getName(), objectName) + "'); END;";
            }
        },

        // SQL translation profiles.
        SQL_TRANSLATION_PROFILE("SQL TRANSLATION PROFILE") {
            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN DBMS_SQL_TRANSLATOR.DROP_PROFILE('" + database.quote(schema.getName(), objectName) + "'); END;";
            }
        },

        // Data mining models, have related objects, should be dropped prior to tables.
        // In Oracle 10g only user-owned models can be dropped.
        MINING_MODEL("MINING MODEL") {
            @Override
            public List getObjectNames(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) throws SQLException {
                if (database.getVersion().isAtLeast("11")) {
                    return super.getObjectNames(jdbcTemplate, database, schema);
                }
                if (schema.isDefaultSchemaForUser() && database.isDataMiningAvailable()) {
                    return jdbcTemplate.queryForStringList("SELECT NAME FROM DM_USER_MODELS");
                }
                return Collections.emptyList();
            }

            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN DBMS_DATA_MINING.DROP_MODEL('" +
                        (database.getVersion().isAtLeast("11") ?
                                database.quote(schema.getName(), objectName)
                                : objectName)
                        + "'); END;";
            }
        },

        // Scheduler objects.
        SCHEDULER_JOB("JOB") {
            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN DBMS_SCHEDULER.DROP_JOB('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
            }
        },
        SCHEDULER_PROGRAM("PROGRAM") {
            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN DBMS_SCHEDULER.DROP_PROGRAM('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
            }
        },
        SCHEDULE("SCHEDULE") {
            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN DBMS_SCHEDULER.DROP_SCHEDULE('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
            }
        },
        SCHEDULER_CHAIN("CHAIN") {
            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN DBMS_SCHEDULER.DROP_CHAIN('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
            }
        },
        FILE_WATCHER("FILE WATCHER") {
            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN DBMS_SCHEDULER.DROP_FILE_WATCHER('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
            }
        },

        // Streams/rule objects.
        RULE_SET("RULE SET") {
            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN DBMS_RULE_ADM.DROP_RULE_SET('" + database.quote(schema.getName(), objectName) + "', DELETE_RULES => FALSE); END;";
            }
        },
        RULE("RULE") {
            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN DBMS_RULE_ADM.DROP_RULE('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
            }
        },
        EVALUATION_CONTEXT("EVALUATION CONTEXT") {
            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN DBMS_RULE_ADM.DROP_EVALUATION_CONTEXT('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
            }
        },
        FILE_GROUP("FILE GROUP") {
            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN DBMS_FILE_GROUP.DROP_FILE_GROUP('" + database.quote(schema.getName(), objectName) + "'); END;";
            }
        },

        /*** Below are unsupported object types. They should be dropped explicitly in callbacks if used. ***/

        // Database links and credentials, contain sensitive information (password) and hence not always can be re-created.
        // Intentionally skip them and let the clean callbacks handle them if needed.
        DATABASE_LINK("DATABASE LINK") {
            @Override
            public void dropObjects(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) {
                super.warnUnsupported(database.quote(schema.getName()));
            }

            @Override
            public List getObjectNames(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) throws SQLException {
                return jdbcTemplate.queryForStringList(
                        "SELECT DB_LINK FROM " + database.dbaOrAll("DB_LINKS") + " WHERE OWNER = ?",
                        schema.getName()
                );
            }

            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "DROP " + this.getName() + " " + objectName; // db link name is case-insensitive and needs no owner
            }
        },
        CREDENTIAL("CREDENTIAL") {
            @Override
            public void dropObjects(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) {
                super.warnUnsupported(database.quote(schema.getName()));
            }

            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN DBMS_SCHEDULER.DROP_CREDENTIAL('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
            }
        },

        // Some scheduler types, not supported yet.
        DATABASE_DESTINATION("DESTINATION") {
            @Override
            public void dropObjects(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) {
                super.warnUnsupported(database.quote(schema.getName()));
            }

            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN DBMS_SCHEDULER.DROP_DATABASE_DESTINATION('" + database.quote(schema.getName(), objectName) + "'); END;";
            }
        },
        SCHEDULER_GROUP("SCHEDULER GROUP") {
            @Override
            public void dropObjects(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) {
                super.warnUnsupported(database.quote(schema.getName()));
            }

            @Override
            public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
                return "BEGIN DBMS_SCHEDULER.DROP_GROUP('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
            }
        },

        // OLAP objects, not supported yet.
        CUBE("CUBE") {
            @Override
            public void dropObjects(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) {
                super.warnUnsupported(database.quote(schema.getName()));
            }
        },
        CUBE_DIMENSION("CUBE DIMENSION") {
            @Override
            public void dropObjects(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) {
                super.warnUnsupported(database.quote(schema.getName()));
            }
        },
        CUBE_BUILD_PROCESS("CUBE BUILD PROCESS") {
            @Override
            public void dropObjects(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) {
                super.warnUnsupported(database.quote(schema.getName()), "cube build processes");
            }
        },
        MEASURE_FOLDER("MEASURE FOLDER") {
            @Override
            public void dropObjects(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) {
                super.warnUnsupported(database.quote(schema.getName()));
            }
        },

        // Undocumented objects.
        ASSEMBLY("ASSEMBLY") {
            @Override
            public void dropObjects(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) {
                super.warnUnsupported(database.quote(schema.getName()), "assemblies");
            }
        },
        JAVA_DATA("JAVA DATA") {
            @Override
            public void dropObjects(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) {
                super.warnUnsupported(database.quote(schema.getName()));
            }
        },

        // SYS-owned objects, cannot be dropped when a schema gets cleaned, simply ignore them.
        CAPTURE("CAPTURE"),
        APPLY("APPLY"),
        DIRECTORY("DIRECTORY"),
        RESOURCE_PLAN("RESOURCE PLAN"),
        CONSUMER_GROUP("CONSUMER GROUP"),
        JOB_CLASS("JOB CLASS"),
        WINDOWS("WINDOW"),
        EDITION("EDITION"),
        AGENT_DESTINATION("DESTINATION"),
        UNIFIED_AUDIT_POLICY("UNIFIED AUDIT POLICY");

        /**
         * The name of the type as it mentioned in the Data Dictionary and the DROP statement.
         */
        private final String name;

        /**
         * The extra options used in the DROP statement to enforce the operation.
         */
        private final String dropOptions;

        ObjectType(String name, String dropOptions) {
            this.name = name;
            this.dropOptions = dropOptions;
        }

        ObjectType(String name) {
            this(name, "");
        }

        public String getName() {
            return name;
        }

        @Override
        public String toString() {
            return super.toString().replace('_', ' ');
        }

        /**
         * Returns the list of object names of this type.
         *
         * @throws SQLException if retrieving of objects failed.
         */
        public List getObjectNames(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) throws SQLException {
            return jdbcTemplate.queryForStringList(
                    "SELECT DISTINCT OBJECT_NAME FROM ALL_OBJECTS WHERE OWNER = ? AND OBJECT_TYPE = ?",
                    schema.getName(), this.getName()
            );
        }

        /**
         * Generates the drop statement for the specified object.
         */
        public String generateDropStatement(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema, String objectName) {
            return "DROP " + this.getName() + " " + database.quote(schema.getName(), objectName) +
                    (StringUtils.hasText(dropOptions) ? " " + dropOptions : "");
        }

        /**
         * Drops all objects of this type in the specified schema.
         *
         * @throws SQLException if cleaning failed.
         */
        public void dropObjects(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) throws SQLException {
            for (String objectName : getObjectNames(jdbcTemplate, database, schema)) {
                jdbcTemplate.execute(generateDropStatement(jdbcTemplate, database, schema, objectName));
            }
        }

        private void warnUnsupported(String schemaName, String typeDesc) {
            LOG.warn("Unable to clean " + typeDesc + " for schema " + schemaName + ": unsupported operation");
        }

        private void warnUnsupported(String schemaName) {
            warnUnsupported(schemaName, this.toString().toLowerCase() + "s");
        }

        /**
         * Returns the schema's existing object types.
         *
         * @return a set of object type names.
         * @throws SQLException if retrieving of object types failed.
         */
        public static Set getObjectTypeNames(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema)
                throws SQLException {
            // Most object types can be correctly selected from DBA_/ALL_OBJECTS.
            // 某模式下所有表名 需要DBA权限
            // String query = "select SEGMENT_NAME as tbName from dba_segments where segment_type='TABLE' and OWNER =?";
            // Github issue: 5【上面的 sql 需要 DBA 权限才行,下面这个不用】
            String query = "SELECT DISTINCT OBJECT_NAME FROM ALL_OBJECTS WHERE OWNER=? AND OBJECT_TYPE='TABLE'";
            int n = 1;
            String[] params = new String[n];
            Arrays.fill(params, schema.getName());
            return new HashSet<>(jdbcTemplate.queryForStringList(query, params));
        }

        @Deprecated
        public static Set getObjectTypeNamesDeprecated(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) throws SQLException {
            boolean xmlDbAvailable = database.isXmlDbAvailable();
            boolean oracle11gOrHigher = database.getVersion().isAtLeast("11");
            boolean dataMining10gForCurrentUser =
                    schema.isDefaultSchemaForUser()
                            && !oracle11gOrHigher
                            && database.isDataMiningAvailable();

            String query =
                    // Most object types can be correctly selected from DBA_/ALL_OBJECTS.
                    "SELECT DISTINCT OBJECT_TYPE FROM " + database.dbaOrAll("OBJECTS") + " WHERE OWNER = ? " +
                            // Materialized view logs.
                            "UNION SELECT '" + MATERIALIZED_VIEW_LOG.getName() + "' FROM DUAL WHERE EXISTS(" +
                            "SELECT * FROM ALL_MVIEW_LOGS WHERE LOG_OWNER = ?) " +
                            // Dimensions.
                            "UNION SELECT '" + DIMENSION.getName() + "' FROM DUAL WHERE EXISTS(" +
                            "SELECT * FROM ALL_DIMENSIONS WHERE OWNER = ?) " +
                            // Queue tables.
                            "UNION SELECT '" + QUEUE_TABLE.getName() + "' FROM DUAL WHERE EXISTS(" +
                            "SELECT * FROM ALL_QUEUE_TABLES WHERE OWNER = ?) " +
                            // Database links.
                            "UNION SELECT '" + DATABASE_LINK.getName() + "' FROM DUAL WHERE EXISTS(" +
                            "SELECT * FROM " + database.dbaOrAll("DB_LINKS") + " WHERE OWNER = ?) " +
                            // Contexts.
                            "UNION SELECT '" + CONTEXT.getName() + "' FROM DUAL WHERE EXISTS(" +
                            "SELECT * FROM " + database.dbaOrAll("CONTEXT") + " WHERE SCHEMA = ?) " +
                            // XML schemas.
                            (xmlDbAvailable
                                    ? "UNION SELECT '" + XML_SCHEMA.getName() + "' FROM DUAL WHERE EXISTS(" +
                                    "SELECT * FROM " + database.dbaOrAll("XML_SCHEMAS") + " WHERE OWNER = ?) "
                                    : "") +
                            // Credentials.
                            (oracle11gOrHigher ?
                                    "UNION SELECT '" + CREDENTIAL.getName() + "' FROM DUAL WHERE EXISTS(" +
                                            "SELECT * FROM " +
                                            (database.getVersion().isAtLeast("12.1") ? "ALL_CREDENTIALS" : "ALL_SCHEDULER_CREDENTIALS")
                                            + " WHERE OWNER = ?) "
                                    : "")
                            // Mining models in Oracle 10.
                            + (dataMining10gForCurrentUser
                            ? "UNION SELECT '" + MINING_MODEL.getName() + "' FROM DUAL WHERE EXISTS(" +
                            "SELECT * FROM DM_USER_MODELS) "
                            : "");

            int n = 6 + (xmlDbAvailable ? 1 : 0) + (oracle11gOrHigher ? 1 : 0);
            String[] params = new String[n];
            Arrays.fill(params, schema.getName());

            return new HashSet<>(jdbcTemplate.queryForStringList(query, params));
        }

        /**
         * Checks whether the specified schema contains object types that can be cleaned.
         *
         * @return {@code true} if it contains, {@code false} if not.
         * @throws SQLException if retrieving of object types failed.
         */
        public static boolean supportedTypesExist(JdbcTemplate jdbcTemplate, DmDatabase database, DmSchema schema) throws SQLException {
            Set existingTypeNames = new HashSet<>(getObjectTypeNames(jdbcTemplate, database, schema));

            // Remove unsupported types.
            existingTypeNames.removeAll(Arrays.asList(
                    DATABASE_LINK.getName(),
                    CREDENTIAL.getName(),
                    DATABASE_DESTINATION.getName(),
                    SCHEDULER_GROUP.getName(),
                    CUBE.getName(),
                    CUBE_DIMENSION.getName(),
                    CUBE_BUILD_PROCESS.getName(),
                    MEASURE_FOLDER.getName(),
                    ASSEMBLY.getName(),
                    JAVA_DATA.getName()
            ));

            return !existingTypeNames.isEmpty();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy